001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.*;
024
025import com.nimbusds.jwt.JWT;
026import com.nimbusds.jwt.JWTParser;
027import com.nimbusds.langtag.LangTag;
028import com.nimbusds.langtag.LangTagException;
029import com.nimbusds.oauth2.sdk.*;
030import com.nimbusds.oauth2.sdk.http.HTTPRequest;
031import com.nimbusds.oauth2.sdk.id.ClientID;
032import com.nimbusds.oauth2.sdk.id.State;
033import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
034import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
035import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
036import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
037import com.nimbusds.oauth2.sdk.util.URIUtils;
038import com.nimbusds.oauth2.sdk.util.URLUtils;
039import com.nimbusds.openid.connect.sdk.claims.ACR;
040import net.jcip.annotations.Immutable;
041import net.minidev.json.JSONObject;
042import org.apache.commons.lang3.StringUtils;
043
044
045/**
046 * OpenID Connect authentication request. Intended to authenticate an end-user
047 * and request the end-user's authorisation to release information to the
048 * client. Supports custom request parameters.
049 *
050 * <p>Example HTTP request (code flow):
051 *
052 * <pre>
053 * https://server.example.com/op/authorize?
054 * response_type=code%20id_token
055 * &amp;client_id=s6BhdRkqt3
056 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
057 * &amp;scope=openid
058 * &amp;nonce=n-0S6_WzA2Mj
059 * &amp;state=af0ifjsldkj
060 * </pre>
061 *
062 * <p>Related specifications:
063 *
064 * <ul>
065 *     <li>OpenID Connect Core 1.0, section 3.1.2.1.
066 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
067 * </ul>
068 */
069@Immutable
070public class AuthenticationRequest extends AuthorizationRequest {
071
072
073        /**
074         * The registered parameter names.
075         */
076        private static final Set<String> REGISTERED_PARAMETER_NAMES;
077
078
079        /**
080         * Initialises the registered parameter name set.
081         */
082        static {
083                Set<String> p = new HashSet<>();
084
085                p.addAll(AuthorizationRequest.getRegisteredParameterNames());
086
087                p.add("nonce");
088                p.add("display");
089                p.add("prompt");
090                p.add("max_age");
091                p.add("ui_locales");
092                p.add("claims_locales");
093                p.add("id_token_hint");
094                p.add("login_hint");
095                p.add("acr_values");
096                p.add("claims");
097                p.add("request_uri");
098                p.add("request");
099
100                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
101        }
102        
103        
104        /**
105         * The nonce (required for implicit flow, optional for code flow).
106         */
107        private final Nonce nonce;
108        
109        
110        /**
111         * The requested display type (optional).
112         */
113        private final Display display;
114        
115        
116        /**
117         * The requested prompt (optional).
118         */
119        private final Prompt prompt;
120
121
122        /**
123         * The required maximum authentication age, in seconds, -1 if not
124         * specified, zero implies prompt=login (optional).
125         */
126        private final int maxAge;
127
128
129        /**
130         * The end-user's preferred languages and scripts for the user 
131         * interface (optional).
132         */
133        private final List<LangTag> uiLocales;
134
135
136        /**
137         * The end-user's preferred languages and scripts for claims being 
138         * returned (optional).
139         */
140        private final List<LangTag> claimsLocales;
141
142
143        /**
144         * Previously issued ID Token passed to the authorisation server as a 
145         * hint about the end-user's current or past authenticated session with
146         * the client (optional). Should be present when {@code prompt=none} is 
147         * used.
148         */
149        private final JWT idTokenHint;
150
151
152        /**
153         * Hint to the authorisation server about the login identifier the 
154         * end-user may use to log in (optional).
155         */
156        private final String loginHint;
157
158
159        /**
160         * Requested Authentication Context Class Reference values (optional).
161         */
162        private final List<ACR> acrValues;
163
164
165        /**
166         * Individual claims to be returned (optional).
167         */
168        private final ClaimsRequest claims;
169        
170        
171        /**
172         * Request object (optional).
173         */
174        private final JWT requestObject;
175        
176        
177        /**
178         * Request object URI (optional).
179         */
180        private final URI requestURI;
181
182
183        /**
184         * Builder for constructing OpenID Connect authentication requests.
185         */
186        public static class Builder {
187
188
189                /**
190                 * The endpoint URI (optional).
191                 */
192                private URI uri;
193
194
195                /**
196                 * The response type (required).
197                 */
198                private final ResponseType rt;
199
200
201                /**
202                 * The client identifier (required).
203                 */
204                private final ClientID clientID;
205
206
207                /**
208                 * The redirection URI where the response will be sent
209                 * (required).
210                 */
211                private final URI redirectURI;
212
213
214                /**
215                 * The scope (required).
216                 */
217                private final Scope scope;
218
219
220                /**
221                 * The opaque value to maintain state between the request and
222                 * the callback (recommended).
223                 */
224                private State state;
225
226
227                /**
228                 * The nonce (required for implicit flow, optional for code
229                 * flow).
230                 */
231                private Nonce nonce;
232
233
234                /**
235                 * The requested display type (optional).
236                 */
237                private Display display;
238
239
240                /**
241                 * The requested prompt (optional).
242                 */
243                private Prompt prompt;
244
245
246                /**
247                 * The required maximum authentication age, in seconds, -1 if
248                 * not specified, zero implies prompt=login (optional).
249                 */
250                private int maxAge = -1;
251
252
253                /**
254                 * The end-user's preferred languages and scripts for the user
255                 * interface (optional).
256                 */
257                private List<LangTag> uiLocales;
258
259
260                /**
261                 * The end-user's preferred languages and scripts for claims
262                 * being returned (optional).
263                 */
264                private List<LangTag> claimsLocales;
265
266
267                /**
268                 * Previously issued ID Token passed to the authorisation
269                 * server as a hint about the end-user's current or past
270                 * authenticated session with the client (optional). Should be
271                 * present when {@code prompt=none} is used.
272                 */
273                private JWT idTokenHint;
274
275
276                /**
277                 * Hint to the authorisation server about the login identifier
278                 * the end-user may use to log in (optional).
279                 */
280                private String loginHint;
281
282
283                /**
284                 * Requested Authentication Context Class Reference values
285                 * (optional).
286                 */
287                private List<ACR> acrValues;
288
289
290                /**
291                 * Individual claims to be returned (optional).
292                 */
293                private ClaimsRequest claims;
294
295
296                /**
297                 * Request object (optional).
298                 */
299                private JWT requestObject;
300
301
302                /**
303                 * Request object URI (optional).
304                 */
305                private URI requestURI;
306
307
308                /**
309                 * The response mode (optional).
310                 */
311                private ResponseMode rm;
312
313
314                /**
315                 * The authorisation code challenge for PKCE (optional).
316                 */
317                private CodeChallenge codeChallenge;
318
319
320                /**
321                 * The authorisation code challenge method for PKCE (optional).
322                 */
323                private CodeChallengeMethod codeChallengeMethod;
324
325
326                /**
327                 * The additional custom parameters.
328                 */
329                private Map<String,String> customParams = new HashMap<>();
330
331
332                /**
333                 * Creates a new OpenID Connect authentication request builder.
334                 *
335                 * @param rt          The response type. Corresponds to the
336                 *                    {@code response_type} parameter. Must
337                 *                    specify a valid OpenID Connect response
338                 *                    type. Must not be {@code null}.
339                 * @param scope       The request scope. Corresponds to the
340                 *                    {@code scope} parameter. Must contain an
341                 *                    {@link OIDCScopeValue#OPENID openid
342                 *                    value}. Must not be {@code null}.
343                 * @param clientID    The client identifier. Corresponds to the
344                 *                    {@code client_id} parameter. Must not be
345                 *                    {@code null}.
346                 * @param redirectURI The redirection URI. Corresponds to the
347                 *                    {@code redirect_uri} parameter. Must not
348                 *                    be {@code null} unless set by means of
349                 *                    the optional {@code request_object} /
350                 *                    {@code request_uri} parameter.
351                 */
352                public Builder(final ResponseType rt,
353                               final Scope scope,
354                               final ClientID clientID,
355                               final URI redirectURI) {
356
357                        if (rt == null)
358                                throw new IllegalArgumentException("The response type must not be null");
359
360                        OIDCResponseTypeValidator.validate(rt);
361
362                        this.rt = rt;
363
364                        if (scope == null)
365                                throw new IllegalArgumentException("The scope must not be null");
366
367                        if (! scope.contains(OIDCScopeValue.OPENID))
368                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
369
370                        this.scope = scope;
371
372                        if (clientID == null)
373                                throw new IllegalArgumentException("The client ID must not be null");
374
375                        this.clientID = clientID;
376
377                        // Check presence at build time
378                        this.redirectURI = redirectURI;
379                }
380
381
382                /**
383                 * Sets the state. Corresponds to the recommended {@code state}
384                 * parameter.
385                 *
386                 * @param state The state, {@code null} if not specified.
387                 *
388                 * @return This builder.
389                 */
390                public Builder state(final State state) {
391
392                        this.state = state;
393                        return this;
394                }
395
396
397                /**
398                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
399                 * request is intended.
400                 *
401                 * @param uri The endpoint URI, {@code null} if not specified.
402                 *
403                 * @return This builder.
404                 */
405                public Builder endpointURI(final URI uri) {
406
407                        this.uri = uri;
408                        return this;
409                }
410
411
412                /**
413                 * Sets the nonce. Corresponds to the conditionally optional
414                 * {@code nonce} parameter.
415                 *
416                 * @param nonce The nonce, {@code null} if not specified.
417                 */
418                public Builder nonce(final Nonce nonce) {
419
420                        this.nonce = nonce;
421                        return this;
422                }
423
424
425                /**
426                 * Sets the requested display type. Corresponds to the optional
427                 * {@code display} parameter.
428                 *
429                 * @param display The requested display type, {@code null} if
430                 *                not specified.
431                 */
432                public Builder display(final Display display) {
433
434                        this.display = display;
435                        return this;
436                }
437
438
439                /**
440                 * Sets the requested prompt. Corresponds to the optional
441                 * {@code prompt} parameter.
442                 *
443                 * @param prompt The requested prompt, {@code null} if not
444                 *               specified.
445                 */
446                public Builder prompt(final Prompt prompt) {
447
448                        this.prompt = prompt;
449                        return this;
450                }
451
452
453                /**
454                 * Sets the required maximum authentication age. Corresponds to
455                 * the optional {@code max_age} parameter.
456                 *
457                 * @param maxAge The maximum authentication age, in seconds; 0
458                 *               if not specified.
459                 */
460                public Builder maxAge(final int maxAge) {
461
462                        this.maxAge = maxAge;
463                        return this;
464                }
465
466
467                /**
468                 * Sets the end-user's preferred languages and scripts for the
469                 * user interface, ordered by preference. Corresponds to the
470                 * optional {@code ui_locales} parameter.
471                 *
472                 * @param uiLocales The preferred UI locales, {@code null} if
473                 *                  not specified.
474                 */
475                public Builder uiLocales(final List<LangTag> uiLocales) {
476
477                        this.uiLocales = uiLocales;
478                        return this;
479                }
480
481
482                /**
483                 * Sets the end-user's preferred languages and scripts for the
484                 * claims being returned, ordered by preference. Corresponds to
485                 * the optional {@code claims_locales} parameter.
486                 *
487                 * @param claimsLocales The preferred claims locales,
488                 *                      {@code null} if not specified.
489                 */
490                public Builder claimsLocales(final List<LangTag> claimsLocales) {
491
492                        this.claimsLocales = claimsLocales;
493                        return this;
494                }
495
496
497                /**
498                 * Sets the ID Token hint. Corresponds to the conditionally
499                 * optional {@code id_token_hint} parameter.
500                 *
501                 * @param idTokenHint The ID Token hint, {@code null} if not
502                 *                    specified.
503                 */
504                public Builder idTokenHint(final JWT idTokenHint) {
505
506                        this.idTokenHint = idTokenHint;
507                        return this;
508                }
509
510
511                /**
512                 * Sets the login hint. Corresponds to the optional
513                 * {@code login_hint} parameter.
514                 *
515                 * @param loginHint The login hint, {@code null} if not
516                 *                  specified.
517                 */
518                public Builder loginHint(final String loginHint) {
519
520                        this.loginHint = loginHint;
521                        return this;
522                }
523
524
525                /**
526                 * Sets the requested Authentication Context Class Reference
527                 * values. Corresponds to the optional {@code acr_values}
528                 * parameter.
529                 *
530                 * @param acrValues The requested ACR values, {@code null} if
531                 *                  not specified.
532                 */
533                public Builder acrValues(final List<ACR> acrValues) {
534
535                        this.acrValues = acrValues;
536                        return this;
537                }
538
539
540                /**
541                 * Sets the individual claims to be returned. Corresponds to
542                 * the optional {@code claims} parameter.
543                 *
544                 * @param claims The individual claims to be returned,
545                 *               {@code null} if not specified.
546                 */
547                public Builder claims(final ClaimsRequest claims) {
548
549                        this.claims = claims;
550                        return this;
551                }
552
553
554                /**
555                 * Sets the request object. Corresponds to the optional
556                 * {@code request} parameter. Must not be specified together
557                 * with a request object URI.
558                 *
559                 * @return The request object, {@code null} if not specified.
560                 */
561                public Builder requestObject(final JWT requestObject) {
562
563                        this.requestObject = requestObject;
564                        return this;
565                }
566
567
568                /**
569                 * Sets the request object URI. Corresponds to the optional
570                 * {@code request_uri} parameter. Must not be specified
571                 * together with a request object.
572                 *
573                 * @param requestURI The request object URI, {@code null} if
574                 *                   not specified.
575                 */
576                public Builder requestURI(final URI requestURI) {
577
578                        this.requestURI = requestURI;
579                        return this;
580                }
581
582
583                /**
584                 * Sets the response mode. Corresponds to the optional
585                 * {@code response_mode} parameter. Use of this parameter is
586                 * not recommended unless a non-default response mode is
587                 * requested (e.g. form_post).
588                 *
589                 * @param rm The response mode, {@code null} if not specified.
590                 *
591                 * @return This builder.
592                 */
593                public Builder responseMode(final ResponseMode rm) {
594
595                        this.rm = rm;
596                        return this;
597                }
598                
599                
600                /**
601                 * Sets the code challenge for Proof Key for Code Exchange
602                 * (PKCE) by public OAuth clients.
603                 *
604                 * @param codeChallenge       The code challenge, {@code null}
605                 *                            if not specified.
606                 * @param codeChallengeMethod The code challenge method,
607                 *                            {@code null} if not specified.
608                 *
609                 * @return This builder.
610                 */
611                @Deprecated
612                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
613                        
614                        this.codeChallenge = codeChallenge;
615                        this.codeChallengeMethod = codeChallengeMethod;
616                        return this;
617                }
618                
619                
620                /**
621                 * Sets the code challenge for Proof Key for Code Exchange
622                 * (PKCE) by public OAuth clients.
623                 *
624                 * @param codeVerifier        The code verifier to use to
625                 *                            compute the code challenge,
626                 *                            {@code null} if PKCE is not
627                 *                            specified.
628                 * @param codeChallengeMethod The code challenge method,
629                 *                            {@code null} if not specified.
630                 *                            Defaults to
631                 *                            {@link CodeChallengeMethod#PLAIN}
632                 *                            if a code verifier is specified.
633                 *
634                 * @return This builder.
635                 */
636                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
637                        
638                        if (codeVerifier != null) {
639                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
640                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
641                                this.codeChallengeMethod = method;
642                        } else {
643                                this.codeChallenge = null;
644                                this.codeChallengeMethod = null;
645                        }
646                        return this;
647                }
648
649
650                /**
651                 * Sets the specified additional custom parameter.
652                 *
653                 * @param name  The parameter name. Must not be {@code null}.
654                 * @param value The parameter value, {@code null} if not
655                 *              specified.
656                 *
657                 * @return This builder.
658                 */
659                public Builder customParameter(final String name, final String value) {
660
661                        customParams.put(name, value);
662                        return this;
663                }
664
665
666                /**
667                 * Builds a new authentication request.
668                 *
669                 * @return The authentication request.
670                 */
671                public AuthenticationRequest build() {
672
673                        try {
674                                return new AuthenticationRequest(
675                                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
676                                        display, prompt, maxAge, uiLocales, claimsLocales,
677                                        idTokenHint, loginHint, acrValues, claims,
678                                        requestObject, requestURI,
679                                        codeChallenge, codeChallengeMethod,
680                                        customParams);
681
682                        } catch (IllegalArgumentException e) {
683
684                                throw new IllegalStateException(e.getMessage(), e);
685                        }
686                }
687        }
688        
689        
690        /**
691         * Creates a new minimal OpenID Connect authentication request.
692         *
693         * @param uri         The URI of the OAuth 2.0 authorisation endpoint.
694         *                    May be {@code null} if the {@link #toHTTPRequest}
695         *                    method will not be used.
696         * @param rt          The response type. Corresponds to the 
697         *                    {@code response_type} parameter. Must specify a
698         *                    valid OpenID Connect response type. Must not be
699         *                    {@code null}.
700         * @param scope       The request scope. Corresponds to the
701         *                    {@code scope} parameter. Must contain an
702         *                    {@link OIDCScopeValue#OPENID openid value}. Must
703         *                    not be {@code null}.
704         * @param clientID    The client identifier. Corresponds to the
705         *                    {@code client_id} parameter. Must not be 
706         *                    {@code null}.
707         * @param redirectURI The redirection URI. Corresponds to the
708         *                    {@code redirect_uri} parameter. Must not be 
709         *                    {@code null}.
710         * @param state       The state. Corresponds to the {@code state}
711         *                    parameter. May be {@code null}.
712         * @param nonce       The nonce. Corresponds to the {@code nonce} 
713         *                    parameter. May be {@code null} for code flow.
714         */
715        public AuthenticationRequest(final URI uri,
716                                     final ResponseType rt,
717                                     final Scope scope,
718                                     final ClientID clientID,
719                                     final URI redirectURI,
720                                     final State state,
721                                     final Nonce nonce) {
722
723                // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 
724                // idTokenHint, loginHint, acrValues, claims
725                // codeChallenge, codeChallengeMethod
726                this(uri, rt, null, scope, clientID, redirectURI, state, nonce,
727                     null, null, -1, null, null,
728                     null, null, null, null, null, null,
729                        null, null);
730        }
731        
732        
733        /**
734         * Creates a new OpenID Connect authentication request.
735         *
736         * @param uri                 The URI of the OAuth 2.0 authorisation
737         *                            endpoint. May be {@code null} if the
738         *                            {@link #toHTTPRequest} method will not be
739         *                            used.
740         * @param rt                  The response type set. Corresponds to the
741         *                            {@code response_type} parameter. Must
742         *                            specify a valid OpenID Connect response
743         *                            type. Must not be {@code null}.
744         * @param rm                  The response mode. Corresponds to the
745         *                            optional {@code response_mode} parameter.
746         *                            Use of this parameter is not recommended
747         *                            unless a non-default response mode is
748         *                            requested (e.g. form_post).
749         * @param scope               The request scope. Corresponds to the
750         *                            {@code scope} parameter. Must contain an
751         *                            {@link OIDCScopeValue#OPENID openid value}.
752         *                            Must not be {@code null}.
753         * @param clientID            The client identifier. Corresponds to the
754         *                            {@code client_id} parameter. Must not be
755         *                            {@code null}.
756         * @param redirectURI         The redirection URI. Corresponds to the
757         *                            {@code redirect_uri} parameter. Must not
758         *                            be {@code null} unless set by means of
759         *                            the optional {@code request_object} /
760         *                            {@code request_uri} parameter.
761         * @param state               The state. Corresponds to the recommended
762         *                            {@code state} parameter. {@code null} if
763         *                            not specified.
764         * @param nonce               The nonce. Corresponds to the
765         *                            {@code nonce} parameter. May be
766         *                            {@code null} for code flow.
767         * @param display             The requested display type. Corresponds
768         *                            to the optional {@code display}
769         *                            parameter.
770         *                            {@code null} if not specified.
771         * @param prompt              The requested prompt. Corresponds to the
772         *                            optional {@code prompt} parameter.
773         *                            {@code null} if not specified.
774         * @param maxAge              The required maximum authentication age,
775         *                            in seconds. Corresponds to the optional
776         *                            {@code max_age} parameter. Zero if not
777         *                            specified.
778         * @param uiLocales           The preferred languages and scripts for
779         *                            the user interface. Corresponds to the
780         *                            optional {@code ui_locales} parameter.
781         *                            {@code null} if not specified.
782         * @param claimsLocales       The preferred languages and scripts for
783         *                            claims being returned. Corresponds to the
784         *                            optional {@code claims_locales}
785         *                            parameter. {@code null} if not specified.
786         * @param idTokenHint         The ID Token hint. Corresponds to the
787         *                            optional {@code id_token_hint} parameter.
788         *                            {@code null} if not specified.
789         * @param loginHint           The login hint. Corresponds to the
790         *                            optional {@code login_hint} parameter.
791         *                            {@code null} if not specified.
792         * @param acrValues           The requested Authentication Context
793         *                            Class Reference values. Corresponds to
794         *                            the optional {@code acr_values}
795         *                            parameter. {@code null} if not specified.
796         * @param claims              The individual claims to be returned.
797         *                            Corresponds to the optional
798         *                            {@code claims} parameter. {@code null} if
799         *                            not specified.
800         * @param requestObject       The request object. Corresponds to the
801         *                            optional {@code request} parameter. Must
802         *                            not be specified together with a request
803         *                            object URI. {@code null} if not
804         *                            specified.
805         * @param requestURI          The request object URI. Corresponds to
806         *                            the optional {@code request_uri}
807         *                            parameter. Must not be specified together
808         *                            with a request object. {@code null} if
809         *                            not specified.
810         * @param codeChallenge       The code challenge for PKCE, {@code null}
811         *                            if not specified.
812         * @param codeChallengeMethod The code challenge method for PKCE,
813         *                            {@code null} if not specified.
814         */
815        public AuthenticationRequest(final URI uri,
816                                     final ResponseType rt,
817                                     final ResponseMode rm,
818                                     final Scope scope,
819                                     final ClientID clientID,
820                                     final URI redirectURI,
821                                     final State state,
822                                     final Nonce nonce,
823                                     final Display display,
824                                     final Prompt prompt,
825                                     final int maxAge,
826                                     final List<LangTag> uiLocales,
827                                     final List<LangTag> claimsLocales,
828                                     final JWT idTokenHint,
829                                     final String loginHint,
830                                     final List<ACR> acrValues,
831                                     final ClaimsRequest claims,
832                                     final JWT requestObject,
833                                     final URI requestURI,
834                                     final CodeChallenge codeChallenge,
835                                     final CodeChallengeMethod codeChallengeMethod) {
836
837                this(uri, rt, rm, scope, clientID, redirectURI, state,
838                        nonce, display, prompt, maxAge, uiLocales, claimsLocales,
839                        idTokenHint, loginHint, acrValues, claims,
840                        requestObject, requestURI, codeChallenge, codeChallengeMethod,
841                        Collections.<String, String>emptyMap());
842        }
843
844
845        /**
846         * Creates a new OpenID Connect authentication request with additional
847         * custom parameters.
848         *
849         * @param uri                 The URI of the OAuth 2.0 authorisation
850         *                            endpoint. May be {@code null} if the
851         *                            {@link #toHTTPRequest} method will not be
852         *                            used.
853         * @param rt                  The response type set. Corresponds to the
854         *                            {@code response_type} parameter. Must
855         *                            specify a valid OpenID Connect response
856         *                            type. Must not be {@code null}.
857         * @param rm                  The response mode. Corresponds to the
858         *                            optional {@code response_mode} parameter.
859         *                            Use of this parameter is not recommended
860         *                            unless a non-default response mode is
861         *                            requested (e.g. form_post).
862         * @param scope               The request scope. Corresponds to the
863         *                            {@code scope} parameter. Must contain an
864         *                            {@link OIDCScopeValue#OPENID openid value}.
865         *                            Must not be {@code null}.
866         * @param clientID            The client identifier. Corresponds to the
867         *                            {@code client_id} parameter. Must not be
868         *                            {@code null}.
869         * @param redirectURI         The redirection URI. Corresponds to the
870         *                            {@code redirect_uri} parameter. Must not
871         *                            be {@code null} unless set by means of
872         *                            the optional {@code request_object} /
873         *                            {@code request_uri} parameter.
874         * @param state               The state. Corresponds to the recommended
875         *                            {@code state} parameter. {@code null} if
876         *                            not specified.
877         * @param nonce               The nonce. Corresponds to the
878         *                            {@code nonce} parameter. May be
879         *                            {@code null} for code flow.
880         * @param display             The requested display type. Corresponds
881         *                            to the optional {@code display}
882         *                            parameter.
883         *                            {@code null} if not specified.
884         * @param prompt              The requested prompt. Corresponds to the
885         *                            optional {@code prompt} parameter.
886         *                            {@code null} if not specified.
887         * @param maxAge              The required maximum authentication age,
888         *                            in seconds. Corresponds to the optional
889         *                            {@code max_age} parameter. -1 if not
890         *                            specified, zero implies
891         *                            {@code prompt=login}.
892         * @param uiLocales           The preferred languages and scripts for
893         *                            the user interface. Corresponds to the
894         *                            optional {@code ui_locales} parameter.
895         *                            {@code null} if not specified.
896         * @param claimsLocales       The preferred languages and scripts for
897         *                            claims being returned. Corresponds to the
898         *                            optional {@code claims_locales}
899         *                            parameter. {@code null} if not specified.
900         * @param idTokenHint         The ID Token hint. Corresponds to the
901         *                            optional {@code id_token_hint} parameter.
902         *                            {@code null} if not specified.
903         * @param loginHint           The login hint. Corresponds to the
904         *                            optional {@code login_hint} parameter.
905         *                            {@code null} if not specified.
906         * @param acrValues           The requested Authentication Context
907         *                            Class Reference values. Corresponds to
908         *                            the optional {@code acr_values}
909         *                            parameter. {@code null} if not specified.
910         * @param claims              The individual claims to be returned.
911         *                            Corresponds to the optional
912         *                            {@code claims} parameter. {@code null} if
913         *                            not specified.
914         * @param requestObject       The request object. Corresponds to the
915         *                            optional {@code request} parameter. Must
916         *                            not be specified together with a request
917         *                            object URI. {@code null} if not
918         *                            specified.
919         * @param requestURI          The request object URI. Corresponds to
920         *                            the optional {@code request_uri}
921         *                            parameter. Must not be specified together
922         *                            with a request object. {@code null} if
923         *                            not specified.
924         * @param codeChallenge       The code challenge for PKCE, {@code null}
925         *                            if not specified.
926         * @param codeChallengeMethod The code challenge method for PKCE,
927         *                            {@code null} if not specified.
928         * @param customParams        Additional custom parameters, empty map
929         *                            or {@code null} if none.
930         */
931        public AuthenticationRequest(final URI uri,
932                                     final ResponseType rt,
933                                     final ResponseMode rm,
934                                     final Scope scope,
935                                     final ClientID clientID,
936                                     final URI redirectURI,
937                                     final State state,
938                                     final Nonce nonce,
939                                     final Display display,
940                                     final Prompt prompt,
941                                     final int maxAge,
942                                     final List<LangTag> uiLocales,
943                                     final List<LangTag> claimsLocales,
944                                     final JWT idTokenHint,
945                                     final String loginHint,
946                                     final List<ACR> acrValues,
947                                     final ClaimsRequest claims,
948                                     final JWT requestObject,
949                                     final URI requestURI,
950                                     final CodeChallenge codeChallenge,
951                                     final CodeChallengeMethod codeChallengeMethod,
952                                     final Map<String,String> customParams) {
953
954                super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams);
955
956                // Redirect URI required unless set in request_object / request_uri
957                if (redirectURI == null && requestObject == null && requestURI == null)
958                        throw new IllegalArgumentException("The redirection URI must not be null");
959                
960                OIDCResponseTypeValidator.validate(rt);
961
962                if (scope == null)
963                        throw new IllegalArgumentException("The scope must not be null");
964
965                if (! scope.contains(OIDCScopeValue.OPENID))
966                        throw new IllegalArgumentException("The scope must include an \"openid\" token");
967                
968                
969                // Nonce required for implicit protocol flow
970                if (rt.impliesImplicitFlow() && nonce == null)
971                        throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow");
972                
973                this.nonce = nonce;
974                
975                // Optional parameters
976                this.display = display;
977                this.prompt = prompt;
978                this.maxAge = maxAge;
979
980                if (uiLocales != null)
981                        this.uiLocales = Collections.unmodifiableList(uiLocales);
982                else
983                        this.uiLocales = null;
984
985                if (claimsLocales != null)
986                        this.claimsLocales = Collections.unmodifiableList(claimsLocales);
987                else
988                        this.claimsLocales = null;
989
990                this.idTokenHint = idTokenHint;
991                this.loginHint = loginHint;
992
993                if (acrValues != null)
994                        this.acrValues = Collections.unmodifiableList(acrValues);
995                else
996                        this.acrValues = null;
997
998                this.claims = claims;
999
1000                if (requestObject != null && requestURI != null)
1001                        throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both");
1002
1003                this.requestObject = requestObject;
1004                this.requestURI = requestURI;
1005        }
1006
1007
1008        /**
1009         * Returns the registered (standard) OpenID Connect authentication
1010         * request parameter names.
1011         *
1012         * @return The registered OpenID Connect authentication request
1013         *         parameter names, as a unmodifiable set.
1014         */
1015        public static Set<String> getRegisteredParameterNames() {
1016
1017                return REGISTERED_PARAMETER_NAMES;
1018        }
1019        
1020        
1021        /**
1022         * Gets the nonce. Corresponds to the conditionally optional 
1023         * {@code nonce} parameter.
1024         *
1025         * @return The nonce, {@code null} if not specified.
1026         */
1027        public Nonce getNonce() {
1028        
1029                return nonce;
1030        }
1031        
1032        
1033        /**
1034         * Gets the requested display type. Corresponds to the optional
1035         * {@code display} parameter.
1036         *
1037         * @return The requested display type, {@code null} if not specified.
1038         */
1039        public Display getDisplay() {
1040        
1041                return display;
1042        }
1043        
1044        
1045        /**
1046         * Gets the requested prompt. Corresponds to the optional 
1047         * {@code prompt} parameter.
1048         *
1049         * @return The requested prompt, {@code null} if not specified.
1050         */
1051        public Prompt getPrompt() {
1052        
1053                return prompt;
1054        }
1055
1056
1057        /**
1058         * Gets the required maximum authentication age. Corresponds to the
1059         * optional {@code max_age} parameter.
1060         *
1061         * @return The maximum authentication age, in seconds; -1 if not
1062         *         specified, zero implies {@code prompt=login}.
1063         */
1064        public int getMaxAge() {
1065        
1066                return maxAge;
1067        }
1068
1069
1070        /**
1071         * Gets the end-user's preferred languages and scripts for the user
1072         * interface, ordered by preference. Corresponds to the optional
1073         * {@code ui_locales} parameter.
1074         *
1075         * @return The preferred UI locales, {@code null} if not specified.
1076         */
1077        public List<LangTag> getUILocales() {
1078
1079                return uiLocales;
1080        }
1081
1082
1083        /**
1084         * Gets the end-user's preferred languages and scripts for the claims
1085         * being returned, ordered by preference. Corresponds to the optional
1086         * {@code claims_locales} parameter.
1087         *
1088         * @return The preferred claims locales, {@code null} if not specified.
1089         */
1090        public List<LangTag> getClaimsLocales() {
1091
1092                return claimsLocales;
1093        }
1094
1095
1096        /**
1097         * Gets the ID Token hint. Corresponds to the conditionally optional 
1098         * {@code id_token_hint} parameter.
1099         *
1100         * @return The ID Token hint, {@code null} if not specified.
1101         */
1102        public JWT getIDTokenHint() {
1103        
1104                return idTokenHint;
1105        }
1106
1107
1108        /**
1109         * Gets the login hint. Corresponds to the optional {@code login_hint} 
1110         * parameter.
1111         *
1112         * @return The login hint, {@code null} if not specified.
1113         */
1114        public String getLoginHint() {
1115
1116                return loginHint;
1117        }
1118
1119
1120        /**
1121         * Gets the requested Authentication Context Class Reference values.
1122         * Corresponds to the optional {@code acr_values} parameter.
1123         *
1124         * @return The requested ACR values, {@code null} if not specified.
1125         */
1126        public List<ACR> getACRValues() {
1127
1128                return acrValues;
1129        }
1130
1131
1132        /**
1133         * Gets the individual claims to be returned. Corresponds to the 
1134         * optional {@code claims} parameter.
1135         *
1136         * @return The individual claims to be returned, {@code null} if not
1137         *         specified.
1138         */
1139        public ClaimsRequest getClaims() {
1140
1141                return claims;
1142        }
1143        
1144        
1145        /**
1146         * Gets the request object. Corresponds to the optional {@code request} 
1147         * parameter.
1148         *
1149         * @return The request object, {@code null} if not specified.
1150         */
1151        public JWT getRequestObject() {
1152        
1153                return requestObject;
1154        }
1155        
1156        
1157        /**
1158         * Gets the request object URI. Corresponds to the optional
1159         * {@code request_uri} parameter.
1160         *
1161         * @return The request object URI, {@code null} if not specified.
1162         */
1163        public URI getRequestURI() {
1164        
1165                return requestURI;
1166        }
1167        
1168        
1169        /**
1170         * Returns {@code true} if this authentication request specifies an
1171         * OpenID Connect request object (directly through the {@code request} 
1172         * parameter or by reference through the {@code request_uri} parameter).
1173         *
1174         * @return {@code true} if a request object is specified, else 
1175         *         {@code false}.
1176         */
1177        public boolean specifiesRequestObject() {
1178        
1179                return requestObject != null || requestURI != null;
1180        }
1181
1182
1183        @Override
1184        public Map<String,String> toParameters() {
1185
1186                Map <String,String> params = super.toParameters();
1187                
1188                if (nonce != null)
1189                        params.put("nonce", nonce.toString());
1190                
1191                if (display != null)
1192                        params.put("display", display.toString());
1193                
1194                if (prompt != null)
1195                        params.put("prompt", prompt.toString());
1196
1197                if (maxAge >= 0)
1198                        params.put("max_age", "" + maxAge);
1199
1200                if (uiLocales != null) {
1201
1202                        StringBuilder sb = new StringBuilder();
1203
1204                        for (LangTag locale: uiLocales) {
1205
1206                                if (sb.length() > 0)
1207                                        sb.append(' ');
1208
1209                                sb.append(locale.toString());
1210                        }
1211
1212                        params.put("ui_locales", sb.toString());
1213                }
1214
1215                if (claimsLocales != null) {
1216
1217                        StringBuilder sb = new StringBuilder();
1218
1219                        for (LangTag locale: claimsLocales) {
1220
1221                                if (sb.length() > 0)
1222                                        sb.append(' ');
1223
1224                                sb.append(locale.toString());
1225                        }
1226
1227                        params.put("claims_locales", sb.toString());
1228                }
1229
1230                if (idTokenHint != null) {
1231                
1232                        try {
1233                                params.put("id_token_hint", idTokenHint.serialize());
1234                                
1235                        } catch (IllegalStateException e) {
1236                        
1237                                throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e);
1238                        }
1239                }
1240
1241                if (loginHint != null)
1242                        params.put("login_hint", loginHint);
1243
1244                if (acrValues != null) {
1245
1246                        StringBuilder sb = new StringBuilder();
1247
1248                        for (ACR acr: acrValues) {
1249
1250                                if (sb.length() > 0)
1251                                        sb.append(' ');
1252
1253                                sb.append(acr.toString());
1254                        }
1255
1256                        params.put("acr_values", sb.toString());
1257                }
1258                        
1259
1260                if (claims != null)
1261                        params.put("claims", claims.toJSONObject().toString());
1262                
1263                if (requestObject != null) {
1264                
1265                        try {
1266                                params.put("request", requestObject.serialize());
1267                                
1268                        } catch (IllegalStateException e) {
1269                        
1270                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1271                        }
1272                }
1273                
1274                if (requestURI != null)
1275                        params.put("request_uri", requestURI.toString());
1276
1277                return params;
1278        }
1279
1280
1281        /**
1282         * Parses an OpenID Connect authentication request from the specified
1283         * parameters.
1284         *
1285         * <p>Example parameters:
1286         *
1287         * <pre>
1288         * response_type = token id_token
1289         * client_id     = s6BhdRkqt3
1290         * redirect_uri  = https://client.example.com/cb
1291         * scope         = openid profile
1292         * state         = af0ifjsldkj
1293         * nonce         = -0S6_WzA2Mj
1294         * </pre>
1295         *
1296         * @param params The parameters. Must not be {@code null}.
1297         *
1298         * @return The OpenID Connect authentication request.
1299         *
1300         * @throws ParseException If the parameters couldn't be parsed to an
1301         *                        OpenID Connect authentication request.
1302         */
1303        public static AuthenticationRequest parse(final Map<String,String> params)
1304                throws ParseException {
1305
1306                return parse(null, params);
1307        }
1308
1309
1310        /**
1311         * Parses an OpenID Connect authentication request from the specified
1312         * parameters.
1313         *
1314         * <p>Example parameters:
1315         *
1316         * <pre>
1317         * response_type = token id_token
1318         * client_id     = s6BhdRkqt3
1319         * redirect_uri  = https://client.example.com/cb
1320         * scope         = openid profile
1321         * state         = af0ifjsldkj
1322         * nonce         = -0S6_WzA2Mj
1323         * </pre>
1324         *
1325         * @param uri    The URI of the OAuth 2.0 authorisation endpoint. May
1326         *               be {@code null} if the {@link #toHTTPRequest} method
1327         *               will not be used.
1328         * @param params The parameters. Must not be {@code null}.
1329         *
1330         * @return The OpenID Connect authentication request.
1331         *
1332         * @throws ParseException If the parameters couldn't be parsed to an
1333         *                        OpenID Connect authentication request.
1334         */
1335        public static AuthenticationRequest parse(final URI uri, final Map<String,String> params)
1336                throws ParseException {
1337
1338                // Parse and validate the core OAuth 2.0 autz request params in 
1339                // the context of OIDC
1340                AuthorizationRequest ar = AuthorizationRequest.parse(uri, params);
1341
1342                ClientID clientID = ar.getClientID();
1343                State state = ar.getState();
1344                ResponseMode rm = ar.getResponseMode();
1345
1346                // Required in OIDC, check later after optional request_object / request_uri is parsed
1347                URI redirectURI = ar.getRedirectionURI();
1348
1349                ResponseType rt = ar.getResponseType();
1350                
1351                try {
1352                        OIDCResponseTypeValidator.validate(rt);
1353                        
1354                } catch (IllegalArgumentException e) {
1355                        String msg = "Unsupported \"response_type\" parameter: " + e.getMessage();
1356                        throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg),
1357                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1358                }
1359                
1360                // Required in OIDC, must include "openid" parameter
1361                Scope scope = ar.getScope();
1362
1363                if (scope == null) {
1364                        String msg = "Missing \"scope\" parameter";
1365                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1366                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1367                }
1368
1369                if (! scope.contains(OIDCScopeValue.OPENID)) {
1370                        String msg = "The scope must include an \"openid\" value";
1371                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1372                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1373                }
1374
1375
1376                // Parse the remaining OIDC parameters
1377                Nonce nonce = Nonce.parse(params.get("nonce"));
1378                
1379                // Nonce required in implicit flow
1380                if (rt.impliesImplicitFlow() && nonce == null) {
1381                        String msg = "Missing \"nonce\" parameter: Required in implicit flow";
1382                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1383                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1384                }
1385                
1386                Display display = null;
1387
1388                if (params.containsKey("display")) {
1389                        try {
1390                                display = Display.parse(params.get("display"));
1391
1392                        } catch (ParseException e) {
1393                                String msg = "Invalid \"display\" parameter: " + e.getMessage();
1394                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1395                                        clientID, redirectURI, ar.impliedResponseMode(), state, e);
1396                        }
1397                }
1398                
1399                
1400                Prompt prompt;
1401                
1402                try {
1403                        prompt = Prompt.parse(params.get("prompt"));
1404                                
1405                } catch (ParseException e) {
1406                        String msg = "Invalid \"prompt\" parameter: " + e.getMessage();
1407                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1408                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1409                }
1410
1411
1412                String v = params.get("max_age");
1413
1414                int maxAge = -1;
1415
1416                if (StringUtils.isNotBlank(v)) {
1417
1418                        try {
1419                                maxAge = Integer.parseInt(v);
1420
1421                        } catch (NumberFormatException e) {
1422                                String msg = "Invalid \"max_age\" parameter: " + v;
1423                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1424                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1425                        }
1426                }
1427
1428
1429                v = params.get("ui_locales");
1430
1431                List<LangTag> uiLocales = null;
1432
1433                if (StringUtils.isNotBlank(v)) {
1434
1435                        uiLocales = new LinkedList<>();
1436
1437                        StringTokenizer st = new StringTokenizer(v, " ");
1438
1439                        while (st.hasMoreTokens()) {
1440
1441                                try {
1442                                        uiLocales.add(LangTag.parse(st.nextToken()));
1443
1444                                } catch (LangTagException e) {
1445                                        String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage();
1446                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1447                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1448                                }
1449                        }
1450                }
1451
1452
1453                v = params.get("claims_locales");
1454
1455                List<LangTag> claimsLocales = null;
1456
1457                if (StringUtils.isNotBlank(v)) {
1458
1459                        claimsLocales = new LinkedList<>();
1460
1461                        StringTokenizer st = new StringTokenizer(v, " ");
1462
1463                        while (st.hasMoreTokens()) {
1464
1465                                try {
1466                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1467
1468                                } catch (LangTagException e) {
1469                                        String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage();
1470                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1471                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1472                                }
1473                        }
1474                }
1475
1476
1477                v = params.get("id_token_hint");
1478                
1479                JWT idTokenHint = null;
1480                
1481                if (StringUtils.isNotBlank(v)) {
1482                
1483                        try {
1484                                idTokenHint = JWTParser.parse(v);
1485                                
1486                        } catch (java.text.ParseException e) {
1487                                String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage();
1488                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1489                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1490                        }
1491                }
1492
1493                String loginHint = params.get("login_hint");
1494
1495
1496                v = params.get("acr_values");
1497
1498                List<ACR> acrValues = null;
1499
1500                if (StringUtils.isNotBlank(v)) {
1501
1502                        acrValues = new LinkedList<>();
1503
1504                        StringTokenizer st = new StringTokenizer(v, " ");
1505
1506                        while (st.hasMoreTokens()) {
1507
1508                                acrValues.add(new ACR(st.nextToken()));
1509                        }
1510                }
1511
1512
1513                v = params.get("claims");
1514
1515                ClaimsRequest claims = null;
1516
1517                if (StringUtils.isNotBlank(v)) {
1518
1519                        JSONObject jsonObject;
1520
1521                        try {
1522                                jsonObject = JSONObjectUtils.parse(v);
1523
1524                        } catch (ParseException e) {
1525                                String msg = "Invalid \"claims\" parameter: " + e.getMessage();
1526                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1527                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1528                        }
1529
1530                        // Parse exceptions silently ignored
1531                        claims = ClaimsRequest.parse(jsonObject);
1532                }
1533                
1534                
1535                v = params.get("request_uri");
1536                
1537                URI requestURI = null;
1538                
1539                if (StringUtils.isNotBlank(v)) {
1540
1541                        try {
1542                                requestURI = new URI(v);
1543                
1544                        } catch (URISyntaxException e) {
1545                                String msg = "Invalid \"request_uri\" parameter: " + e.getMessage();
1546                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1547                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1548                        }
1549                }
1550
1551                v = params.get("request");
1552
1553                JWT requestObject = null;
1554
1555                if (StringUtils.isNotBlank(v)) {
1556
1557                        // request_object and request_uri must not be defined at the same time
1558                        if (requestURI != null) {
1559                                String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters";
1560                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1561                                                         clientID, redirectURI, ar.impliedResponseMode(), state, null);
1562                        }
1563
1564                        try {
1565                                requestObject = JWTParser.parse(v);
1566                                
1567                        } catch (java.text.ParseException e) {
1568                                String msg = "Invalid \"request_object\" parameter: " + e.getMessage();
1569                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1570                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1571                        }
1572                }
1573
1574
1575                // Redirect URI required unless request_object / request_uri present
1576                if (redirectURI == null && requestObject == null && requestURI == null) {
1577                        String msg = "Missing \"redirect_uri\" parameter";
1578                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1579                                clientID, null, ar.impliedResponseMode(), state);
1580                }
1581
1582                // Parse additional custom parameters
1583                Map<String,String> customParams = null;
1584
1585                for (Map.Entry<String,String> p: params.entrySet()) {
1586
1587                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1588                                // We have a custom parameter
1589                                if (customParams == null) {
1590                                        customParams = new HashMap<>();
1591                                }
1592                                customParams.put(p.getKey(), p.getValue());
1593                        }
1594                }
1595
1596
1597                return new AuthenticationRequest(
1598                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
1599                        display, prompt, maxAge, uiLocales, claimsLocales,
1600                        idTokenHint, loginHint, acrValues, claims, requestObject, requestURI,
1601                        ar.getCodeChallenge(), ar.getCodeChallengeMethod(),
1602                        customParams);
1603        }
1604        
1605        
1606        /**
1607         * Parses an OpenID Connect authentication request from the specified
1608         * URI query string.
1609         *
1610         * <p>Example URI query string:
1611         *
1612         * <pre>
1613         * response_type=token%20id_token
1614         * &amp;client_id=s6BhdRkqt3
1615         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1616         * &amp;scope=openid%20profile
1617         * &amp;state=af0ifjsldkj
1618         * &amp;nonce=n-0S6_WzA2Mj
1619         * </pre>
1620         *
1621         * @param query The URI query string. Must not be {@code null}.
1622         *
1623         * @return The OpenID Connect authentication request.
1624         *
1625         * @throws ParseException If the query string couldn't be parsed to an 
1626         *                        OpenID Connect authentication request.
1627         */
1628        public static AuthenticationRequest parse(final String query)
1629                throws ParseException {
1630        
1631                return parse(null, URLUtils.parseParameters(query));
1632        }
1633
1634
1635        /**
1636         * Parses an OpenID Connect authentication request from the specified
1637         * URI query string.
1638         *
1639         * <p>Example URI query string:
1640         *
1641         * <pre>
1642         * response_type=token%20id_token
1643         * &amp;client_id=s6BhdRkqt3
1644         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1645         * &amp;scope=openid%20profile
1646         * &amp;state=af0ifjsldkj
1647         * &amp;nonce=n-0S6_WzA2Mj
1648         * </pre>
1649         *
1650         * @param uri   The URI of the OAuth 2.0 authorisation endpoint. May be
1651         *              {@code null} if the {@link #toHTTPRequest} method will
1652         *              not be used.
1653         * @param query The URI query string. Must not be {@code null}.
1654         *
1655         * @return The OpenID Connect authentication request.
1656         *
1657         * @throws ParseException If the query string couldn't be parsed to an
1658         *                        OpenID Connect authentication request.
1659         */
1660        public static AuthenticationRequest parse(final URI uri, final String query)
1661                throws ParseException {
1662
1663                return parse(uri, URLUtils.parseParameters(query));
1664        }
1665
1666
1667        /**
1668         * Parses an OpenID Connect authentication request from the specified
1669         * URI.
1670         *
1671         * <p>Example URI:
1672         *
1673         * <pre>
1674         * https://server.example.com/authorize?
1675         * response_type=token%20id_token
1676         * &amp;client_id=s6BhdRkqt3
1677         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1678         * &amp;scope=openid%20profile
1679         * &amp;state=af0ifjsldkj
1680         * &amp;nonce=n-0S6_WzA2Mj
1681         * </pre>
1682         *
1683         * @param uri The URI. Must not be {@code null}.
1684         *
1685         * @return The OpenID Connect authentication request.
1686         *
1687         * @throws ParseException If the query string couldn't be parsed to an
1688         *                        OpenID Connect authentication request.
1689         */
1690        public static AuthenticationRequest parse(final URI uri)
1691                throws ParseException {
1692
1693                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1694        }
1695        
1696        
1697        /**
1698         * Parses an authentication request from the specified HTTP GET or HTTP
1699         * POST request.
1700         *
1701         * <p>Example HTTP request (GET):
1702         *
1703         * <pre>
1704         * https://server.example.com/op/authorize?
1705         * response_type=code%20id_token
1706         * &amp;client_id=s6BhdRkqt3
1707         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1708         * &amp;scope=openid
1709         * &amp;nonce=n-0S6_WzA2Mj
1710         * &amp;state=af0ifjsldkj
1711         * </pre>
1712         *
1713         * @param httpRequest The HTTP request. Must not be {@code null}.
1714         *
1715         * @return The OpenID Connect authentication request.
1716         *
1717         * @throws ParseException If the HTTP request couldn't be parsed to an 
1718         *                        OpenID Connect authentication request.
1719         */
1720        public static AuthenticationRequest parse(final HTTPRequest httpRequest)
1721                throws ParseException {
1722                
1723                String query = httpRequest.getQuery();
1724                
1725                if (query == null)
1726                        throw new ParseException("Missing URI query string");
1727
1728                URI endpointURI;
1729
1730                try {
1731                        endpointURI = httpRequest.getURL().toURI();
1732
1733                } catch (URISyntaxException e) {
1734
1735                        throw new ParseException(e.getMessage(), e);
1736                }
1737                
1738                return parse(endpointURI, query);
1739        }
1740}