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