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