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