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                throws SerializeException {
914
915                Map <String,String> params = super.toParameters();
916                
917                if (nonce != null)
918                        params.put("nonce", nonce.toString());
919                
920                if (display != null)
921                        params.put("display", display.toString());
922                
923                if (prompt != null)
924                        params.put("prompt", prompt.toString());
925
926                if (maxAge > 0)
927                        params.put("max_age", "" + maxAge);
928
929                if (uiLocales != null) {
930
931                        StringBuilder sb = new StringBuilder();
932
933                        for (LangTag locale: uiLocales) {
934
935                                if (sb.length() > 0)
936                                        sb.append(' ');
937
938                                sb.append(locale.toString());
939                        }
940
941                        params.put("ui_locales", sb.toString());
942                }
943
944                if (claimsLocales != null) {
945
946                        StringBuilder sb = new StringBuilder();
947
948                        for (LangTag locale: claimsLocales) {
949
950                                if (sb.length() > 0)
951                                        sb.append(' ');
952
953                                sb.append(locale.toString());
954                        }
955
956                        params.put("claims_locales", sb.toString());
957                }
958
959                if (idTokenHint != null) {
960                
961                        try {
962                                params.put("id_token_hint", idTokenHint.serialize());
963                                
964                        } catch (IllegalStateException e) {
965                        
966                                throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e);
967                        }
968                }
969
970                if (loginHint != null)
971                        params.put("login_hint", loginHint);
972
973                if (acrValues != null) {
974
975                        StringBuilder sb = new StringBuilder();
976
977                        for (ACR acr: acrValues) {
978
979                                if (sb.length() > 0)
980                                        sb.append(' ');
981
982                                sb.append(acr.toString());
983                        }
984
985                        params.put("acr_values", sb.toString());
986                }
987                        
988
989                if (claims != null)
990                        params.put("claims", claims.toJSONObject().toString());
991                
992                if (requestObject != null) {
993                
994                        try {
995                                params.put("request", requestObject.serialize());
996                                
997                        } catch (IllegalStateException e) {
998                        
999                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1000                        }
1001                }
1002                
1003                if (requestURI != null)
1004                        params.put("request_uri", requestURI.toString());
1005
1006                return params;
1007        }
1008
1009
1010        /**
1011         * Parses an OpenID Connect authentication request from the specified
1012         * parameters.
1013         *
1014         * <p>Example parameters:
1015         *
1016         * <pre>
1017         * response_type = token id_token
1018         * client_id     = s6BhdRkqt3
1019         * redirect_uri  = https://client.example.com/cb
1020         * scope         = openid profile
1021         * state         = af0ifjsldkj
1022         * nonce         = -0S6_WzA2Mj
1023         * </pre>
1024         *
1025         * @param params The parameters. Must not be {@code null}.
1026         *
1027         * @return The OpenID Connect authentication request.
1028         *
1029         * @throws ParseException If the parameters couldn't be parsed to an
1030         *                        OpenID Connect authentication request.
1031         */
1032        public static AuthenticationRequest parse(final Map<String,String> params)
1033                throws ParseException {
1034
1035                return parse(null, params);
1036        }
1037
1038
1039        /**
1040         * Parses an OpenID Connect authentication request from the specified
1041         * parameters.
1042         *
1043         * <p>Example parameters:
1044         *
1045         * <pre>
1046         * response_type = token id_token
1047         * client_id     = s6BhdRkqt3
1048         * redirect_uri  = https://client.example.com/cb
1049         * scope         = openid profile
1050         * state         = af0ifjsldkj
1051         * nonce         = -0S6_WzA2Mj
1052         * </pre>
1053         *
1054         * @param uri    The URI of the OAuth 2.0 authorisation endpoint. May
1055         *               be {@code null} if the {@link #toHTTPRequest} method
1056         *               will not be used.
1057         * @param params The parameters. Must not be {@code null}.
1058         *
1059         * @return The OpenID Connect authentication request.
1060         *
1061         * @throws ParseException If the parameters couldn't be parsed to an
1062         *                        OpenID Connect authentication request.
1063         */
1064        public static AuthenticationRequest parse(final URI uri, final Map<String,String> params)
1065                throws ParseException {
1066
1067                // Parse and validate the core OAuth 2.0 autz request params in 
1068                // the context of OIDC
1069                AuthorizationRequest ar = AuthorizationRequest.parse(uri, params);
1070
1071                ClientID clientID = ar.getClientID();
1072                State state = ar.getState();
1073                ResponseMode rm = ar.getResponseMode();
1074
1075                // Required in OIDC, check later after optional request_object / request_uri is parsed
1076                URI redirectURI = ar.getRedirectionURI();
1077
1078                ResponseType rt = ar.getResponseType();
1079                
1080                try {
1081                        OIDCResponseTypeValidator.validate(rt);
1082                        
1083                } catch (IllegalArgumentException e) {
1084                        String msg = "Unsupported \"response_type\" parameter: " + e.getMessage();
1085                        throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg),
1086                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1087                }
1088                
1089                // Required in OIDC, must include "openid" parameter
1090                Scope scope = ar.getScope();
1091
1092                if (scope == null) {
1093                        String msg = "Missing \"scope\" parameter";
1094                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1095                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1096                }
1097
1098                if (! scope.contains(OIDCScopeValue.OPENID)) {
1099                        String msg = "The scope must include an \"openid\" value";
1100                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1101                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1102                }
1103
1104
1105                // Parse the remaining OIDC parameters
1106                Nonce nonce = Nonce.parse(params.get("nonce"));
1107                
1108                // Nonce required in implicit flow
1109                if (rt.impliesImplicitFlow() && nonce == null) {
1110                        String msg = "Missing \"nonce\" parameter: Required in implicit flow";
1111                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1112                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1113                }
1114                
1115                Display display;
1116                
1117                try {
1118                        display = Display.parse(params.get("display"));
1119
1120                } catch (ParseException e) {
1121                        String msg = "Invalid \"display\" parameter: " + e.getMessage();
1122                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1123                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1124                }
1125                
1126                
1127                Prompt prompt;
1128                
1129                try {
1130                        prompt = Prompt.parse(params.get("prompt"));
1131                                
1132                } catch (ParseException e) {
1133                        String msg = "Invalid \"prompt\" parameter: " + e.getMessage();
1134                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1135                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1136                }
1137
1138
1139                String v = params.get("max_age");
1140
1141                int maxAge = 0;
1142
1143                if (StringUtils.isNotBlank(v)) {
1144
1145                        try {
1146                                maxAge = Integer.parseInt(v);
1147
1148                        } catch (NumberFormatException e) {
1149                                String msg = "Invalid \"max_age\" parameter: " + v;
1150                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1151                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1152                        }
1153                }
1154
1155
1156                v = params.get("ui_locales");
1157
1158                List<LangTag> uiLocales = null;
1159
1160                if (StringUtils.isNotBlank(v)) {
1161
1162                        uiLocales = new LinkedList<>();
1163
1164                        StringTokenizer st = new StringTokenizer(v, " ");
1165
1166                        while (st.hasMoreTokens()) {
1167
1168                                try {
1169                                        uiLocales.add(LangTag.parse(st.nextToken()));
1170
1171                                } catch (LangTagException e) {
1172                                        String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage();
1173                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1174                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1175                                }
1176                        }
1177                }
1178
1179
1180                v = params.get("claims_locales");
1181
1182                List<LangTag> claimsLocales = null;
1183
1184                if (StringUtils.isNotBlank(v)) {
1185
1186                        claimsLocales = new LinkedList<>();
1187
1188                        StringTokenizer st = new StringTokenizer(v, " ");
1189
1190                        while (st.hasMoreTokens()) {
1191
1192                                try {
1193                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1194
1195                                } catch (LangTagException e) {
1196                                        String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage();
1197                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1198                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1199                                }
1200                        }
1201                }
1202
1203
1204                v = params.get("id_token_hint");
1205                
1206                JWT idTokenHint = null;
1207                
1208                if (StringUtils.isNotBlank(v)) {
1209                
1210                        try {
1211                                idTokenHint = JWTParser.parse(v);
1212                                
1213                        } catch (java.text.ParseException e) {
1214                                String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage();
1215                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1216                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1217                        }
1218                }
1219
1220                String loginHint = params.get("login_hint");
1221
1222
1223                v = params.get("acr_values");
1224
1225                List<ACR> acrValues = null;
1226
1227                if (StringUtils.isNotBlank(v)) {
1228
1229                        acrValues = new LinkedList<>();
1230
1231                        StringTokenizer st = new StringTokenizer(v, " ");
1232
1233                        while (st.hasMoreTokens()) {
1234
1235                                acrValues.add(new ACR(st.nextToken()));
1236                        }
1237                }
1238
1239
1240                v = params.get("claims");
1241
1242                ClaimsRequest claims = null;
1243
1244                if (StringUtils.isNotBlank(v)) {
1245
1246                        JSONObject jsonObject;
1247
1248                        try {
1249                                jsonObject = JSONObjectUtils.parse(v);
1250
1251                        } catch (ParseException e) {
1252                                String msg = "Invalid \"claims\" parameter: " + e.getMessage();
1253                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1254                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1255                        }
1256
1257                        // Parse exceptions silently ignored
1258                        claims = ClaimsRequest.parse(jsonObject);
1259                }
1260                
1261                
1262                v = params.get("request_uri");
1263                
1264                URI requestURI = null;
1265                
1266                if (StringUtils.isNotBlank(v)) {
1267
1268                        try {
1269                                requestURI = new URI(v);
1270                
1271                        } catch (URISyntaxException e) {
1272                                String msg = "Invalid \"request_uri\" parameter: " + e.getMessage();
1273                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1274                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1275                        }
1276                }
1277
1278                v = params.get("request");
1279
1280                JWT requestObject = null;
1281
1282                if (StringUtils.isNotBlank(v)) {
1283
1284                        // request_object and request_uri must not be defined at the same time
1285                        if (requestURI != null) {
1286                                String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters";
1287                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1288                                                         clientID, redirectURI, ar.impliedResponseMode(), state, null);
1289                        }
1290
1291                        try {
1292                                requestObject = JWTParser.parse(v);
1293                                
1294                        } catch (java.text.ParseException e) {
1295                                String msg = "Invalid \"request_object\" parameter: " + e.getMessage();
1296                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1297                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1298                        }
1299                }
1300
1301
1302                // Redirect URI required unless request_object / request_uri present
1303                if (redirectURI == null && requestObject == null && requestURI == null) {
1304                        String msg = "Missing \"redirect_uri\" parameter";
1305                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1306                                clientID, null, ar.impliedResponseMode(), state);
1307                }
1308
1309
1310                return new AuthenticationRequest(
1311                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
1312                        display, prompt, maxAge, uiLocales, claimsLocales,
1313                        idTokenHint, loginHint, acrValues, claims, requestObject, requestURI);
1314        }
1315        
1316        
1317        /**
1318         * Parses an OpenID Connect authentication request from the specified
1319         * URI query string.
1320         *
1321         * <p>Example URI query string:
1322         *
1323         * <pre>
1324         * response_type=token%20id_token
1325         * &amp;client_id=s6BhdRkqt3
1326         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1327         * &amp;scope=openid%20profile
1328         * &amp;state=af0ifjsldkj
1329         * &amp;nonce=n-0S6_WzA2Mj
1330         * </pre>
1331         *
1332         * @param query The URI query string. Must not be {@code null}.
1333         *
1334         * @return The OpenID Connect authentication request.
1335         *
1336         * @throws ParseException If the query string couldn't be parsed to an 
1337         *                        OpenID Connect authentication request.
1338         */
1339        public static AuthenticationRequest parse(final String query)
1340                throws ParseException {
1341        
1342                return parse(null, URLUtils.parseParameters(query));
1343        }
1344
1345
1346        /**
1347         * Parses an OpenID Connect authentication request from the specified
1348         * URI query string.
1349         *
1350         * <p>Example URI query string:
1351         *
1352         * <pre>
1353         * response_type=token%20id_token
1354         * &amp;client_id=s6BhdRkqt3
1355         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1356         * &amp;scope=openid%20profile
1357         * &amp;state=af0ifjsldkj
1358         * &amp;nonce=n-0S6_WzA2Mj
1359         * </pre>
1360         *
1361         * @param uri   The URI of the OAuth 2.0 authorisation endpoint. May be
1362         *              {@code null} if the {@link #toHTTPRequest} method will
1363         *              not be used.
1364         * @param query The URI query string. Must not be {@code null}.
1365         *
1366         * @return The OpenID Connect authentication request.
1367         *
1368         * @throws ParseException If the query string couldn't be parsed to an
1369         *                        OpenID Connect authentication request.
1370         */
1371        public static AuthenticationRequest parse(final URI uri, final String query)
1372                throws ParseException {
1373
1374                return parse(uri, URLUtils.parseParameters(query));
1375        }
1376
1377
1378        /**
1379         * Parses an OpenID Connect authentication request from the specified
1380         * URI.
1381         *
1382         * <p>Example URI:
1383         *
1384         * <pre>
1385         * https://server.example.com/authorize?
1386         * response_type=token%20id_token
1387         * &amp;client_id=s6BhdRkqt3
1388         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1389         * &amp;scope=openid%20profile
1390         * &amp;state=af0ifjsldkj
1391         * &amp;nonce=n-0S6_WzA2Mj
1392         * </pre>
1393         *
1394         * @param uri The URI. Must not be {@code null}.
1395         *
1396         * @return The OpenID Connect authentication request.
1397         *
1398         * @throws ParseException If the query string couldn't be parsed to an
1399         *                        OpenID Connect authentication request.
1400         */
1401        public static AuthenticationRequest parse(final URI uri)
1402                throws ParseException {
1403
1404                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getQuery()));
1405        }
1406        
1407        
1408        /**
1409         * Parses an authentication request from the specified HTTP GET or HTTP
1410         * POST request.
1411         *
1412         * <p>Example HTTP request (GET):
1413         *
1414         * <pre>
1415         * https://server.example.com/op/authorize?
1416         * response_type=code%20id_token
1417         * &amp;client_id=s6BhdRkqt3
1418         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1419         * &amp;scope=openid
1420         * &amp;nonce=n-0S6_WzA2Mj
1421         * &amp;state=af0ifjsldkj
1422         * </pre>
1423         *
1424         * @param httpRequest The HTTP request. Must not be {@code null}.
1425         *
1426         * @return The OpenID Connect authentication request.
1427         *
1428         * @throws ParseException If the HTTP request couldn't be parsed to an 
1429         *                        OpenID Connect authentication request.
1430         */
1431        public static AuthenticationRequest parse(final HTTPRequest httpRequest)
1432                throws ParseException {
1433                
1434                String query = httpRequest.getQuery();
1435                
1436                if (query == null)
1437                        throw new ParseException("Missing URI query string");
1438
1439                URI endpointURI;
1440
1441                try {
1442                        endpointURI = httpRequest.getURL().toURI();
1443
1444                } catch (URISyntaxException e) {
1445
1446                        throw new ParseException(e.getMessage(), e);
1447                }
1448                
1449                return parse(endpointURI, query);
1450        }
1451}