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