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