001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.*;
024
025import net.jcip.annotations.Immutable;
026import net.minidev.json.JSONObject;
027
028import com.nimbusds.jwt.JWT;
029import com.nimbusds.jwt.JWTClaimsSet;
030import com.nimbusds.jwt.JWTParser;
031import com.nimbusds.langtag.LangTag;
032import com.nimbusds.langtag.LangTagException;
033import com.nimbusds.oauth2.sdk.*;
034import com.nimbusds.oauth2.sdk.http.HTTPRequest;
035import com.nimbusds.oauth2.sdk.id.ClientID;
036import com.nimbusds.oauth2.sdk.id.State;
037import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
038import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
039import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
040import com.nimbusds.oauth2.sdk.util.*;
041import com.nimbusds.openid.connect.sdk.claims.ACR;
042
043
044/**
045 * OpenID Connect authentication request. Intended to authenticate an end-user
046 * and request the end-user's authorisation to release information to the
047 * client. Supports custom request parameters.
048 *
049 * <p>Example HTTP request (code flow):
050 *
051 * <pre>
052 * https://server.example.com/op/authorize?
053 * response_type=code%20id_token
054 * &amp;client_id=s6BhdRkqt3
055 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
056 * &amp;scope=openid
057 * &amp;nonce=n-0S6_WzA2Mj
058 * &amp;state=af0ifjsldkj
059 * </pre>
060 *
061 * <p>Related specifications:
062 *
063 * <ul>
064 *     <li>OpenID Connect Core 1.0, section 3.1.2.1.
065 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
066 *     <li>Resource Indicators for OAuth 2.0 (RFC 8707)
067 *     <li>The OAuth 2.0 Authorization Framework: JWT Secured Authorization
068 *         Request (JAR) draft-ietf-oauth-jwsreq-21
069 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
070 *         OAuth 2.0 (JARM)
071 *     <li>OpenID Connect for Identity Assurance 1.0, section 8.
072 * </ul>
073 */
074@Immutable
075public class AuthenticationRequest extends AuthorizationRequest {
076        
077        
078        /**
079         * The purpose string parameter minimal length.
080         */
081        public static final int PURPOSE_MIN_LENGTH = 3;
082        
083        
084        /**
085         * The purpose string parameter maximum length.
086         */
087        public static final int PURPOSE_MAX_LENGTH = 300;
088
089
090        /**
091         * The registered parameter names.
092         */
093        private static final Set<String> REGISTERED_PARAMETER_NAMES;
094
095
096        static {
097                
098                Set<String> p = new HashSet<>(AuthorizationRequest.getRegisteredParameterNames());
099
100                p.add("nonce");
101                p.add("display");
102                p.add("max_age");
103                p.add("ui_locales");
104                p.add("claims_locales");
105                p.add("id_token_hint");
106                p.add("login_hint");
107                p.add("acr_values");
108                p.add("claims");
109                p.add("purpose");
110
111                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
112        }
113        
114        
115        /**
116         * The nonce (required for implicit flow (unless in JAR), optional for
117         * code flow).
118         */
119        private final Nonce nonce;
120        
121        
122        /**
123         * The requested display type (optional).
124         */
125        private final Display display;
126        
127        
128        /**
129         * The required maximum authentication age, in seconds, -1 if not
130         * specified, zero implies prompt=login (optional).
131         */
132        private final int maxAge;
133
134
135        /**
136         * The end-user's preferred languages and scripts for the user 
137         * interface (optional).
138         */
139        private final List<LangTag> uiLocales;
140
141
142        /**
143         * The end-user's preferred languages and scripts for claims being 
144         * returned (optional).
145         */
146        private final List<LangTag> claimsLocales;
147
148
149        /**
150         * Previously issued ID Token passed to the authorisation server as a 
151         * hint about the end-user's current or past authenticated session with
152         * the client (optional). Should be present when {@code prompt=none} is 
153         * used.
154         */
155        private final JWT idTokenHint;
156
157
158        /**
159         * Hint to the authorisation server about the login identifier the 
160         * end-user may use to log in (optional).
161         */
162        private final String loginHint;
163
164
165        /**
166         * Requested Authentication Context Class Reference values (optional).
167         */
168        private final List<ACR> acrValues;
169
170
171        /**
172         * Individual claims to be returned (optional).
173         */
174        private final ClaimsRequest claims;
175        
176        
177        /**
178         * The transaction specific purpose, for use in OpenID Connect Identity
179         * Assurance.
180         */
181        private final String purpose;
182
183
184        /**
185         * Builder for constructing OpenID Connect authentication requests.
186         */
187        public static class Builder {
188
189
190                /**
191                 * The endpoint URI (optional).
192                 */
193                private URI uri;
194
195
196                /**
197                 * The response type (required unless in JAR).
198                 */
199                private ResponseType rt;
200
201
202                /**
203                 * The client identifier (required unless in JAR).
204                 */
205                private final ClientID clientID;
206
207
208                /**
209                 * The redirection URI where the response will be sent
210                 * (required unless in JAR).
211                 */
212                private URI redirectURI;
213
214
215                /**
216                 * The scope (required unless in JAR).
217                 */
218                private Scope scope;
219
220
221                /**
222                 * The opaque value to maintain state between the request and
223                 * the callback (recommended).
224                 */
225                private State state;
226
227
228                /**
229                 * The nonce (required for implicit flow (unless in JAR),
230                 * optional for code flow).
231                 */
232                private Nonce nonce;
233
234
235                /**
236                 * The requested display type (optional).
237                 */
238                private Display display;
239
240
241                /**
242                 * The requested prompt (optional).
243                 */
244                private Prompt prompt;
245
246
247                /**
248                 * The required maximum authentication age, in seconds, -1 if
249                 * not specified, zero implies prompt=login (optional).
250                 */
251                private int maxAge = -1;
252
253
254                /**
255                 * The end-user's preferred languages and scripts for the user
256                 * interface (optional).
257                 */
258                private List<LangTag> uiLocales;
259
260
261                /**
262                 * The end-user's preferred languages and scripts for claims
263                 * being returned (optional).
264                 */
265                private List<LangTag> claimsLocales;
266
267
268                /**
269                 * Previously issued ID Token passed to the authorisation
270                 * server as a hint about the end-user's current or past
271                 * authenticated session with the client (optional). Should be
272                 * present when {@code prompt=none} is used.
273                 */
274                private JWT idTokenHint;
275
276
277                /**
278                 * Hint to the authorisation server about the login identifier
279                 * the end-user may use to log in (optional).
280                 */
281                private String loginHint;
282
283
284                /**
285                 * Requested Authentication Context Class Reference values
286                 * (optional).
287                 */
288                private List<ACR> acrValues;
289
290
291                /**
292                 * Individual claims to be returned (optional).
293                 */
294                private ClaimsRequest claims;
295                
296                
297                /**
298                 * The transaction specific purpose (optional).
299                 */
300                private String purpose;
301
302
303                /**
304                 * Request object (optional).
305                 */
306                private JWT requestObject;
307
308
309                /**
310                 * Request object URI (optional).
311                 */
312                private URI requestURI;
313
314
315                /**
316                 * The response mode (optional).
317                 */
318                private ResponseMode rm;
319
320
321                /**
322                 * The authorisation code challenge for PKCE (optional).
323                 */
324                private CodeChallenge codeChallenge;
325
326
327                /**
328                 * The authorisation code challenge method for PKCE (optional).
329                 */
330                private CodeChallengeMethod codeChallengeMethod;
331                
332                
333                /**
334                 * The resource URI(s) (optional).
335                 */
336                private List<URI> resources;
337                
338                
339                /**
340                 * Indicates incremental authorisation (optional).
341                 */
342                private boolean includeGrantedScopes;
343
344
345                /**
346                 * Custom parameters.
347                 */
348                private final Map<String,List<String>> customParams = new HashMap<>();
349
350
351                /**
352                 * Creates a new OpenID Connect authentication request builder.
353                 *
354                 * @param rt          The response type. Corresponds to the
355                 *                    {@code response_type} parameter. Must
356                 *                    specify a valid OpenID Connect response
357                 *                    type. Must not be {@code null}.
358                 * @param scope       The request scope. Corresponds to the
359                 *                    {@code scope} parameter. Must contain an
360                 *                    {@link OIDCScopeValue#OPENID openid
361                 *                    value}. Must not be {@code null}.
362                 * @param clientID    The client identifier. Corresponds to the
363                 *                    {@code client_id} parameter. Must not be
364                 *                    {@code null}.
365                 * @param redirectURI The redirection URI. Corresponds to the
366                 *                    {@code redirect_uri} parameter. Must not
367                 *                    be {@code null} unless set by means of
368                 *                    the optional {@code request_object} /
369                 *                    {@code request_uri} parameter.
370                 */
371                public Builder(final ResponseType rt,
372                               final Scope scope,
373                               final ClientID clientID,
374                               final URI redirectURI) {
375
376                        if (rt == null)
377                                throw new IllegalArgumentException("The response type must not be null");
378
379                        OIDCResponseTypeValidator.validate(rt);
380
381                        this.rt = rt;
382
383                        if (scope == null)
384                                throw new IllegalArgumentException("The scope must not be null");
385
386                        if (! scope.contains(OIDCScopeValue.OPENID))
387                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
388
389                        this.scope = scope;
390
391                        if (clientID == null)
392                                throw new IllegalArgumentException("The client ID must not be null");
393
394                        this.clientID = clientID;
395
396                        // Check presence at build time
397                        this.redirectURI = redirectURI;
398                }
399
400
401                /**
402                 * Creates a new JWT secured OpenID Connect authentication
403                 * request (JAR) builder.
404                 *
405                 * @param requestObject The request object. Must not be
406                 *                      {@code null}.
407                 * @param clientID      The client ID. Must not be
408                 *                      {@code null}.
409                 */
410                public Builder(final JWT requestObject, final ClientID clientID) {
411                        
412                        if (requestObject == null)
413                                throw new IllegalArgumentException("The request object must not be null");
414
415                        this.requestObject = requestObject;
416                        
417                        if (clientID == null)
418                                throw new IllegalArgumentException("The client ID must not be null");
419                        
420                        this.clientID = clientID;
421                }
422
423
424                /**
425                 * Creates a new JWT secured OpenID Connect authentication
426                 * request (JAR) builder.
427                 *
428                 * @param requestURI The request object URI. Must not be
429                 *                   {@code null}.
430                 * @param clientID   The client ID. Must not be {@code null}.
431                 */
432                public Builder(final URI requestURI, final ClientID clientID) {
433                        
434                        if (requestURI == null)
435                                throw new IllegalArgumentException("The request URI must not be null");
436
437                        this.requestURI = requestURI;
438                        
439                        if (clientID == null)
440                                throw new IllegalArgumentException("The client ID must not be null");
441                        
442                        this.clientID = clientID;
443                }
444                
445                
446                /**
447                 * Creates a new OpenID Connect authentication request builder
448                 * from the specified request.
449                 *
450                 * @param request The OpenID Connect authentication request.
451                 *                Must not be {@code null}.
452                 */
453                public Builder(final AuthenticationRequest request) {
454                        
455                        uri = request.getEndpointURI();
456                        rt = request.getResponseType();
457                        clientID = request.getClientID();
458                        redirectURI = request.getRedirectionURI();
459                        scope = request.getScope();
460                        state = request.getState();
461                        nonce = request.getNonce();
462                        display = request.getDisplay();
463                        prompt = request.getPrompt();
464                        maxAge = request.getMaxAge();
465                        uiLocales = request.getUILocales();
466                        claimsLocales = request.getClaimsLocales();
467                        idTokenHint = request.getIDTokenHint();
468                        loginHint = request.getLoginHint();
469                        acrValues = request.getACRValues();
470                        claims = request.getClaims();
471                        purpose = request.getPurpose();
472                        requestObject = request.getRequestObject();
473                        requestURI = request.getRequestURI();
474                        rm = request.getResponseMode();
475                        codeChallenge = request.getCodeChallenge();
476                        codeChallengeMethod = request.getCodeChallengeMethod();
477                        resources = request.getResources();
478                        includeGrantedScopes = request.includeGrantedScopes();
479                        customParams.putAll(request.getCustomParameters());
480                }
481                
482                
483                /**
484                 * Sets the response type. Corresponds to the
485                 * {@code response_type} parameter.
486                 *
487                 * @param rt The response type. Must not be {@code null}.
488                 *
489                 * @return This builder.
490                 */
491                public Builder responseType(final ResponseType rt) {
492                        
493                        if (rt == null)
494                                throw new IllegalArgumentException("The response type must not be null");
495                        
496                        this.rt = rt;
497                        return this;
498                }
499                
500                
501                /**
502                 * Sets the scope. Corresponds to the {@code scope} parameter.
503                 *
504                 * @param scope The scope. Must not be {@code null}.
505                 *
506                 * @return This builder.
507                 */
508                public Builder scope(final Scope scope) {
509                        
510                        if (scope == null)
511                                throw new IllegalArgumentException("The scope must not be null");
512                        
513                        if (! scope.contains(OIDCScopeValue.OPENID))
514                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
515                        
516                        this.scope = scope;
517                        return this;
518                }
519                
520                
521                /**
522                 * Sets the redirection URI. Corresponds to the
523                 * {@code redirection_uri} parameter.
524                 *
525                 * @param redirectURI The redirection URI. Must not be
526                 *                    {@code null}.
527                 *
528                 * @return This builder.
529                 */
530                public Builder redirectionURI(final URI redirectURI) {
531                        
532                        if (redirectURI == null)
533                                throw new IllegalArgumentException("The redirection URI must not be null");
534                        
535                        this.redirectURI = redirectURI;
536                        return this;
537                }
538
539
540                /**
541                 * Sets the state. Corresponds to the recommended {@code state}
542                 * parameter.
543                 *
544                 * @param state The state, {@code null} if not specified.
545                 *
546                 * @return This builder.
547                 */
548                public Builder state(final State state) {
549
550                        this.state = state;
551                        return this;
552                }
553
554
555                /**
556                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
557                 * request is intended.
558                 *
559                 * @param uri The endpoint URI, {@code null} if not specified.
560                 *
561                 * @return This builder.
562                 */
563                public Builder endpointURI(final URI uri) {
564
565                        this.uri = uri;
566                        return this;
567                }
568
569
570                /**
571                 * Sets the nonce. Corresponds to the conditionally optional
572                 * {@code nonce} parameter.
573                 *
574                 * @param nonce The nonce, {@code null} if not specified.
575                 *
576                 * @return This builder.
577                 */
578                public Builder nonce(final Nonce nonce) {
579
580                        this.nonce = nonce;
581                        return this;
582                }
583
584
585                /**
586                 * Sets the requested display type. Corresponds to the optional
587                 * {@code display} parameter.
588                 *
589                 * @param display The requested display type, {@code null} if
590                 *                not specified.
591                 *
592                 * @return This builder.
593                 */
594                public Builder display(final Display display) {
595
596                        this.display = display;
597                        return this;
598                }
599
600
601                /**
602                 * Sets the requested prompt. Corresponds to the optional
603                 * {@code prompt} parameter.
604                 *
605                 * @param prompt The requested prompt, {@code null} if not
606                 *               specified.
607                 *
608                 * @return This builder.
609                 */
610                public Builder prompt(final Prompt prompt) {
611
612                        this.prompt = prompt;
613                        return this;
614                }
615
616
617                /**
618                 * Sets the required maximum authentication age. Corresponds to
619                 * the optional {@code max_age} parameter.
620                 *
621                 * @param maxAge The maximum authentication age, in seconds; 0
622                 *               if not specified.
623                 *
624                 * @return This builder.
625                 */
626                public Builder maxAge(final int maxAge) {
627
628                        this.maxAge = maxAge;
629                        return this;
630                }
631
632
633                /**
634                 * Sets the end-user's preferred languages and scripts for the
635                 * user interface, ordered by preference. Corresponds to the
636                 * optional {@code ui_locales} parameter.
637                 *
638                 * @param uiLocales The preferred UI locales, {@code null} if
639                 *                  not specified.
640                 *
641                 * @return This builder.
642                 */
643                public Builder uiLocales(final List<LangTag> uiLocales) {
644
645                        this.uiLocales = uiLocales;
646                        return this;
647                }
648
649
650                /**
651                 * Sets the end-user's preferred languages and scripts for the
652                 * claims being returned, ordered by preference. Corresponds to
653                 * the optional {@code claims_locales} parameter.
654                 *
655                 * @param claimsLocales The preferred claims locales,
656                 *                      {@code null} if not specified.
657                 *
658                 * @return This builder.
659                 */
660                public Builder claimsLocales(final List<LangTag> claimsLocales) {
661
662                        this.claimsLocales = claimsLocales;
663                        return this;
664                }
665
666
667                /**
668                 * Sets the ID Token hint. Corresponds to the conditionally
669                 * optional {@code id_token_hint} parameter.
670                 *
671                 * @param idTokenHint The ID Token hint, {@code null} if not
672                 *                    specified.
673                 *
674                 * @return This builder.
675                 */
676                public Builder idTokenHint(final JWT idTokenHint) {
677
678                        this.idTokenHint = idTokenHint;
679                        return this;
680                }
681
682
683                /**
684                 * Sets the login hint. Corresponds to the optional
685                 * {@code login_hint} parameter.
686                 *
687                 * @param loginHint The login hint, {@code null} if not
688                 *                  specified.
689                 *
690                 * @return This builder.
691                 */
692                public Builder loginHint(final String loginHint) {
693
694                        this.loginHint = loginHint;
695                        return this;
696                }
697
698
699                /**
700                 * Sets the requested Authentication Context Class Reference
701                 * values. Corresponds to the optional {@code acr_values}
702                 * parameter.
703                 *
704                 * @param acrValues The requested ACR values, {@code null} if
705                 *                  not specified.
706                 *
707                 * @return This builder.
708                 */
709                public Builder acrValues(final List<ACR> acrValues) {
710
711                        this.acrValues = acrValues;
712                        return this;
713                }
714
715
716                /**
717                 * Sets the individual claims to be returned. Corresponds to
718                 * the optional {@code claims} parameter.
719                 *
720                 * @param claims The individual claims to be returned,
721                 *               {@code null} if not specified.
722                 *
723                 * @return This builder.
724                 */
725                public Builder claims(final ClaimsRequest claims) {
726
727                        this.claims = claims;
728                        return this;
729                }
730                
731                
732                /**
733                 * Sets the transaction specific purpose. Corresponds to the
734                 * optional {@code purpose} parameter.
735                 *
736                 * @param purpose The purpose, {@code null} if not specified.
737                 *
738                 * @return This builder.
739                 */
740                public Builder purpose(final String purpose) {
741                        
742                        this.purpose = purpose;
743                        return this;
744                }
745
746
747                /**
748                 * Sets the request object. Corresponds to the optional
749                 * {@code request} parameter. Must not be specified together
750                 * with a request object URI.
751                 *
752                 * @param requestObject The request object, {@code null} if not
753                 *                      specified.
754                 *
755                 * @return This builder.
756                 */
757                public Builder requestObject(final JWT requestObject) {
758
759                        this.requestObject = requestObject;
760                        return this;
761                }
762
763
764                /**
765                 * Sets the request object URI. Corresponds to the optional
766                 * {@code request_uri} parameter. Must not be specified
767                 * together with a request object.
768                 *
769                 * @param requestURI The request object URI, {@code null} if
770                 *                   not specified.
771                 *
772                 * @return This builder.
773                 */
774                public Builder requestURI(final URI requestURI) {
775
776                        this.requestURI = requestURI;
777                        return this;
778                }
779
780
781                /**
782                 * Sets the response mode. Corresponds to the optional
783                 * {@code response_mode} parameter. Use of this parameter is
784                 * not recommended unless a non-default response mode is
785                 * requested (e.g. form_post).
786                 *
787                 * @param rm The response mode, {@code null} if not specified.
788                 *
789                 * @return This builder.
790                 */
791                public Builder responseMode(final ResponseMode rm) {
792
793                        this.rm = rm;
794                        return this;
795                }
796                
797                
798                /**
799                 * Sets the code challenge for Proof Key for Code Exchange
800                 * (PKCE) by public OAuth clients.
801                 *
802                 * @param codeChallenge       The code challenge, {@code null}
803                 *                            if not specified.
804                 * @param codeChallengeMethod The code challenge method,
805                 *                            {@code null} if not specified.
806                 *
807                 * @return This builder.
808                 */
809                @Deprecated
810                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
811                        
812                        this.codeChallenge = codeChallenge;
813                        this.codeChallengeMethod = codeChallengeMethod;
814                        return this;
815                }
816                
817                
818                /**
819                 * Sets the code challenge for Proof Key for Code Exchange
820                 * (PKCE) by public OAuth clients.
821                 *
822                 * @param codeVerifier        The code verifier to use to
823                 *                            compute the code challenge,
824                 *                            {@code null} if PKCE is not
825                 *                            specified.
826                 * @param codeChallengeMethod The code challenge method,
827                 *                            {@code null} if not specified.
828                 *                            Defaults to
829                 *                            {@link CodeChallengeMethod#PLAIN}
830                 *                            if a code verifier is specified.
831                 *
832                 * @return This builder.
833                 */
834                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
835                        
836                        if (codeVerifier != null) {
837                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
838                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
839                                this.codeChallengeMethod = method;
840                        } else {
841                                this.codeChallenge = null;
842                                this.codeChallengeMethod = null;
843                        }
844                        return this;
845                }
846                
847                
848                /**
849                 * Sets the resource server URI(s).
850                 *
851                 * @param resources The resource URI(s), {@code null} if not
852                 *                  specified.
853                 *
854                 * @return This builder.
855                 */
856                public Builder resources(final URI ... resources) {
857                        if (resources != null) {
858                                this.resources = Arrays.asList(resources);
859                        } else {
860                                this.resources = null;
861                        }
862                        return this;
863                }
864                
865                
866                /**
867                 * Requests incremental authorisation.
868                 *
869                 * @param includeGrantedScopes {@code true} to request
870                 *                             incremental authorisation.
871                 *
872                 * @return This builder.
873                 */
874                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
875                        
876                        this.includeGrantedScopes = includeGrantedScopes;
877                        return this;
878                }
879                
880                
881                /**
882                 * Sets a custom parameter.
883                 *
884                 * @param name   The parameter name. Must not be {@code null}.
885                 * @param values The parameter values, {@code null} if not
886                 *               specified.
887                 *
888                 * @return This builder.
889                 */
890                public Builder customParameter(final String name, final String ... values) {
891                        
892                        if (values == null || values.length == 0) {
893                                customParams.remove(name);
894                        } else {
895                                customParams.put(name, Arrays.asList(values));
896                        }
897                        
898                        return this;
899                }
900
901
902                /**
903                 * Builds a new authentication request.
904                 *
905                 * @return The authentication request.
906                 */
907                public AuthenticationRequest build() {
908
909                        try {
910                                return new AuthenticationRequest(
911                                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
912                                        display, prompt, maxAge, uiLocales, claimsLocales,
913                                        idTokenHint, loginHint, acrValues, claims,
914                                        purpose,
915                                        requestObject, requestURI,
916                                        codeChallenge, codeChallengeMethod,
917                                        resources,
918                                        includeGrantedScopes,
919                                        customParams);
920
921                        } catch (IllegalArgumentException e) {
922                                throw new IllegalStateException(e.getMessage(), e);
923                        }
924                }
925        }
926        
927        
928        /**
929         * Creates a new minimal OpenID Connect authentication request.
930         *
931         * @param uri         The URI of the OAuth 2.0 authorisation endpoint.
932         *                    May be {@code null} if the {@link #toHTTPRequest}
933         *                    method will not be used.
934         * @param rt          The response type. Corresponds to the 
935         *                    {@code response_type} parameter. Must specify a
936         *                    valid OpenID Connect response type. Must not be
937         *                    {@code null}.
938         * @param scope       The request scope. Corresponds to the
939         *                    {@code scope} parameter. Must contain an
940         *                    {@link OIDCScopeValue#OPENID openid value}. Must
941         *                    not be {@code null}.
942         * @param clientID    The client identifier. Corresponds to the
943         *                    {@code client_id} parameter. Must not be 
944         *                    {@code null}.
945         * @param redirectURI The redirection URI. Corresponds to the
946         *                    {@code redirect_uri} parameter. Must not be 
947         *                    {@code null}.
948         * @param state       The state. Corresponds to the {@code state}
949         *                    parameter. May be {@code null}.
950         * @param nonce       The nonce. Corresponds to the {@code nonce} 
951         *                    parameter. May be {@code null} for code flow.
952         */
953        public AuthenticationRequest(final URI uri,
954                                     final ResponseType rt,
955                                     final Scope scope,
956                                     final ClientID clientID,
957                                     final URI redirectURI,
958                                     final State state,
959                                     final Nonce nonce) {
960
961                // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 
962                // idTokenHint, loginHint, acrValues, claims, purpose
963                // codeChallenge, codeChallengeMethod
964                this(uri, rt, null, scope, clientID, redirectURI, state, nonce,
965                        null, null, -1, null, null,
966                        null, null, null, null, null,
967                        null, null,
968                        null, null,
969                        null, false, null);
970        }
971
972
973        /**
974         * Creates a new OpenID Connect authentication request with extension
975         * and custom parameters.
976         *
977         * @param uri                  The URI of the OAuth 2.0 authorisation
978         *                             endpoint. May be {@code null} if the
979         *                             {@link #toHTTPRequest} method will not
980         *                             be used.
981         * @param rt                   The response type set. Corresponds to
982         *                             the {@code response_type} parameter.
983         *                             Must specify a valid OpenID Connect
984         *                             response type. Must not be {@code null}.
985         * @param rm                   The response mode. Corresponds to the
986         *                             optional {@code response_mode}
987         *                             parameter. Use of this parameter is not
988         *                             recommended unless a non-default
989         *                             response mode is requested (e.g.
990         *                             form_post).
991         * @param scope                The request scope. Corresponds to the
992         *                             {@code scope} parameter. Must contain an
993         *                             {@link OIDCScopeValue#OPENID openid
994         *                             value}. Must not be {@code null}.
995         * @param clientID             The client identifier. Corresponds to
996         *                             the {@code client_id} parameter. Must
997         *                             not be {@code null}.
998         * @param redirectURI          The redirection URI. Corresponds to the
999         *                             {@code redirect_uri} parameter. Must not
1000         *                             be {@code null} unless set by means of
1001         *                             the optional {@code request_object} /
1002         *                             {@code request_uri} parameter.
1003         * @param state                The state. Corresponds to the
1004         *                             recommended {@code state} parameter.
1005         *                             {@code null} if not specified.
1006         * @param nonce                The nonce. Corresponds to the
1007         *                             {@code nonce} parameter. May be
1008         *                             {@code null} for code flow.
1009         * @param display              The requested display type. Corresponds
1010         *                             to the optional {@code display}
1011         *                             parameter.
1012         *                             {@code null} if not specified.
1013         * @param prompt               The requested prompt. Corresponds to the
1014         *                             optional {@code prompt} parameter.
1015         *                             {@code null} if not specified.
1016         * @param maxAge               The required maximum authentication age,
1017         *                             in seconds. Corresponds to the optional
1018         *                             {@code max_age} parameter. -1 if not
1019         *                             specified, zero implies
1020         *                             {@code prompt=login}.
1021         * @param uiLocales            The preferred languages and scripts for
1022         *                             the user interface. Corresponds to the
1023         *                             optional {@code ui_locales} parameter.
1024         *                             {@code null} if not specified.
1025         * @param claimsLocales        The preferred languages and scripts for
1026         *                             claims being returned. Corresponds to
1027         *                             the optional {@code claims_locales}
1028         *                             parameter. {@code null} if not
1029         *                             specified.
1030         * @param idTokenHint          The ID Token hint. Corresponds to the
1031         *                             optional {@code id_token_hint}
1032         *                             parameter. {@code null} if not
1033         *                             specified.
1034         * @param loginHint            The login hint. Corresponds to the
1035         *                             optional {@code login_hint} parameter.
1036         *                             {@code null} if not specified.
1037         * @param acrValues            The requested Authentication Context
1038         *                             Class Reference values. Corresponds to
1039         *                             the optional {@code acr_values}
1040         *                             parameter. {@code null} if not
1041         *                             specified.
1042         * @param claims               The individual claims to be returned.
1043         *                             Corresponds to the optional
1044         *                             {@code claims} parameter. {@code null}
1045         *                             if not specified.
1046         * @param purpose              The transaction specific purpose,
1047         *                             {@code null} if not specified.
1048         * @param requestObject        The request object. Corresponds to the
1049         *                             optional {@code request} parameter. Must
1050         *                             not be specified together with a request
1051         *                             object URI. {@code null} if not
1052         *                             specified.
1053         * @param requestURI           The request object URI. Corresponds to
1054         *                             the optional {@code request_uri}
1055         *                             parameter. Must not be specified
1056         *                             together with a request object.
1057         *                             {@code null} if not specified.
1058         * @param codeChallenge        The code challenge for PKCE,
1059         *                             {@code null} if not specified.
1060         * @param codeChallengeMethod  The code challenge method for PKCE,
1061         *                             {@code null} if not specified.
1062         * @param resources            The resource URI(s), {@code null} if not
1063         *                             specified.
1064         * @param includeGrantedScopes {@code true} to request incremental
1065         *                             authorisation.
1066         * @param customParams         Additional custom parameters, empty map
1067         *                             or {@code null} if none.
1068         */
1069        public AuthenticationRequest(final URI uri,
1070                                     final ResponseType rt,
1071                                     final ResponseMode rm,
1072                                     final Scope scope,
1073                                     final ClientID clientID,
1074                                     final URI redirectURI,
1075                                     final State state,
1076                                     final Nonce nonce,
1077                                     final Display display,
1078                                     final Prompt prompt,
1079                                     final int maxAge,
1080                                     final List<LangTag> uiLocales,
1081                                     final List<LangTag> claimsLocales,
1082                                     final JWT idTokenHint,
1083                                     final String loginHint,
1084                                     final List<ACR> acrValues,
1085                                     final ClaimsRequest claims,
1086                                     final String purpose,
1087                                     final JWT requestObject,
1088                                     final URI requestURI,
1089                                     final CodeChallenge codeChallenge,
1090                                     final CodeChallengeMethod codeChallengeMethod,
1091                                     final List<URI> resources,
1092                                     final boolean includeGrantedScopes,
1093                                     final Map<String,List<String>> customParams) {
1094
1095                super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, requestObject, requestURI, prompt, customParams);
1096                
1097                if (! specifiesRequestObject()) {
1098                        
1099                        // Check parameters required by OpenID Connect if no JAR
1100                        
1101                        if (redirectURI == null)
1102                                throw new IllegalArgumentException("The redirection URI must not be null");
1103                        
1104                        OIDCResponseTypeValidator.validate(rt);
1105                        
1106                        if (scope == null)
1107                                throw new IllegalArgumentException("The scope must not be null");
1108                        
1109                        if (!scope.contains(OIDCScopeValue.OPENID))
1110                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
1111                        
1112                        // Nonce required in the implicit and hybrid flows
1113                        if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow()))
1114                                throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow");
1115                }
1116                
1117                this.nonce = nonce;
1118                
1119                // Optional parameters
1120                this.display = display;
1121                this.maxAge = maxAge;
1122
1123                if (uiLocales != null)
1124                        this.uiLocales = Collections.unmodifiableList(uiLocales);
1125                else
1126                        this.uiLocales = null;
1127
1128                if (claimsLocales != null)
1129                        this.claimsLocales = Collections.unmodifiableList(claimsLocales);
1130                else
1131                        this.claimsLocales = null;
1132
1133                this.idTokenHint = idTokenHint;
1134                this.loginHint = loginHint;
1135
1136                if (acrValues != null)
1137                        this.acrValues = Collections.unmodifiableList(acrValues);
1138                else
1139                        this.acrValues = null;
1140
1141                this.claims = claims;
1142                
1143                if (purpose != null) {
1144                        if (purpose.length() < PURPOSE_MIN_LENGTH) {
1145                                throw new IllegalArgumentException("The purpose must not be shorter than " + PURPOSE_MIN_LENGTH + " characters");
1146                        }
1147                        if (purpose.length() > PURPOSE_MAX_LENGTH) {
1148                                throw new IllegalArgumentException("The purpose must not be longer than " + PURPOSE_MAX_LENGTH +" characters");
1149                        }
1150                }
1151                
1152                this.purpose = purpose;
1153        }
1154
1155
1156        /**
1157         * Returns the registered (standard) OpenID Connect authentication
1158         * request parameter names.
1159         *
1160         * @return The registered OpenID Connect authentication request
1161         *         parameter names, as a unmodifiable set.
1162         */
1163        public static Set<String> getRegisteredParameterNames() {
1164
1165                return REGISTERED_PARAMETER_NAMES;
1166        }
1167        
1168        
1169        /**
1170         * Gets the nonce. Corresponds to the conditionally optional 
1171         * {@code nonce} parameter.
1172         *
1173         * @return The nonce, {@code null} if not specified.
1174         */
1175        public Nonce getNonce() {
1176        
1177                return nonce;
1178        }
1179        
1180        
1181        /**
1182         * Gets the requested display type. Corresponds to the optional
1183         * {@code display} parameter.
1184         *
1185         * @return The requested display type, {@code null} if not specified.
1186         */
1187        public Display getDisplay() {
1188        
1189                return display;
1190        }
1191        
1192        
1193        /**
1194         * Gets the required maximum authentication age. Corresponds to the
1195         * optional {@code max_age} parameter.
1196         *
1197         * @return The maximum authentication age, in seconds; -1 if not
1198         *         specified, zero implies {@code prompt=login}.
1199         */
1200        public int getMaxAge() {
1201        
1202                return maxAge;
1203        }
1204
1205
1206        /**
1207         * Gets the end-user's preferred languages and scripts for the user
1208         * interface, ordered by preference. Corresponds to the optional
1209         * {@code ui_locales} parameter.
1210         *
1211         * @return The preferred UI locales, {@code null} if not specified.
1212         */
1213        public List<LangTag> getUILocales() {
1214
1215                return uiLocales;
1216        }
1217
1218
1219        /**
1220         * Gets the end-user's preferred languages and scripts for the claims
1221         * being returned, ordered by preference. Corresponds to the optional
1222         * {@code claims_locales} parameter.
1223         *
1224         * @return The preferred claims locales, {@code null} if not specified.
1225         */
1226        public List<LangTag> getClaimsLocales() {
1227
1228                return claimsLocales;
1229        }
1230
1231
1232        /**
1233         * Gets the ID Token hint. Corresponds to the conditionally optional 
1234         * {@code id_token_hint} parameter.
1235         *
1236         * @return The ID Token hint, {@code null} if not specified.
1237         */
1238        public JWT getIDTokenHint() {
1239        
1240                return idTokenHint;
1241        }
1242
1243
1244        /**
1245         * Gets the login hint. Corresponds to the optional {@code login_hint} 
1246         * parameter.
1247         *
1248         * @return The login hint, {@code null} if not specified.
1249         */
1250        public String getLoginHint() {
1251
1252                return loginHint;
1253        }
1254
1255
1256        /**
1257         * Gets the requested Authentication Context Class Reference values.
1258         * Corresponds to the optional {@code acr_values} parameter.
1259         *
1260         * @return The requested ACR values, {@code null} if not specified.
1261         */
1262        public List<ACR> getACRValues() {
1263
1264                return acrValues;
1265        }
1266
1267
1268        /**
1269         * Gets the individual claims to be returned. Corresponds to the 
1270         * optional {@code claims} parameter.
1271         *
1272         * @return The individual claims to be returned, {@code null} if not
1273         *         specified.
1274         */
1275        public ClaimsRequest getClaims() {
1276
1277                return claims;
1278        }
1279        
1280        
1281        /**
1282         * Gets the transaction specific purpose. Corresponds to the optional
1283         * {@code purpose} parameter.
1284         *
1285         * @return The purpose, {@code null} if not specified.
1286         */
1287        public String getPurpose() {
1288                
1289                return purpose;
1290        }
1291
1292
1293        @Override
1294        public Map<String,List<String>> toParameters() {
1295
1296                Map <String,List<String>> params = super.toParameters();
1297                
1298                if (nonce != null)
1299                        params.put("nonce", Collections.singletonList(nonce.toString()));
1300                
1301                if (display != null)
1302                        params.put("display", Collections.singletonList(display.toString()));
1303
1304                if (maxAge >= 0)
1305                        params.put("max_age", Collections.singletonList("" + maxAge));
1306
1307                if (uiLocales != null) {
1308
1309                        StringBuilder sb = new StringBuilder();
1310
1311                        for (LangTag locale: uiLocales) {
1312
1313                                if (sb.length() > 0)
1314                                        sb.append(' ');
1315
1316                                sb.append(locale.toString());
1317                        }
1318
1319                        params.put("ui_locales", Collections.singletonList(sb.toString()));
1320                }
1321
1322                if (claimsLocales != null) {
1323
1324                        StringBuilder sb = new StringBuilder();
1325
1326                        for (LangTag locale: claimsLocales) {
1327
1328                                if (sb.length() > 0)
1329                                        sb.append(' ');
1330
1331                                sb.append(locale.toString());
1332                        }
1333
1334                        params.put("claims_locales", Collections.singletonList(sb.toString()));
1335                }
1336
1337                if (idTokenHint != null) {
1338                
1339                        try {
1340                                params.put("id_token_hint", Collections.singletonList(idTokenHint.serialize()));
1341                                
1342                        } catch (IllegalStateException e) {
1343                        
1344                                throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e);
1345                        }
1346                }
1347
1348                if (loginHint != null)
1349                        params.put("login_hint", Collections.singletonList(loginHint));
1350
1351                if (acrValues != null) {
1352
1353                        StringBuilder sb = new StringBuilder();
1354
1355                        for (ACR acr: acrValues) {
1356
1357                                if (sb.length() > 0)
1358                                        sb.append(' ');
1359
1360                                sb.append(acr.toString());
1361                        }
1362
1363                        params.put("acr_values", Collections.singletonList(sb.toString()));
1364                }
1365                        
1366
1367                if (claims != null)
1368                        params.put("claims", Collections.singletonList(claims.toJSONObject().toString()));
1369                
1370                if (purpose != null)
1371                        params.put("purpose", Collections.singletonList(purpose));
1372
1373                return params;
1374        }
1375        
1376        
1377        @Override
1378        public JWTClaimsSet toJWTClaimsSet() {
1379                
1380                JWTClaimsSet jwtClaimsSet = super.toJWTClaimsSet();
1381                
1382                if (jwtClaimsSet.getClaim("max_age") != null) {
1383                        // Convert max_age to number in JSON object
1384                        try {
1385                                String maxAgeString = jwtClaimsSet.getStringClaim("max_age");
1386                                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(jwtClaimsSet);
1387                                builder.claim("max_age", Integer.parseInt(maxAgeString));
1388                                return builder.build();
1389                        } catch (java.text.ParseException e) {
1390                                throw new SerializeException(e.getMessage());
1391                        }
1392                }
1393                
1394                return jwtClaimsSet;
1395        }
1396        
1397        
1398        /**
1399         * Parses an OpenID Connect authentication request from the specified
1400         * URI query parameters.
1401         *
1402         * <p>Example parameters:
1403         *
1404         * <pre>
1405         * response_type = token id_token
1406         * client_id     = s6BhdRkqt3
1407         * redirect_uri  = https://client.example.com/cb
1408         * scope         = openid profile
1409         * state         = af0ifjsldkj
1410         * nonce         = -0S6_WzA2Mj
1411         * </pre>
1412         *
1413         * @param params The parameters. Must not be {@code null}.
1414         *
1415         * @return The OpenID Connect authentication request.
1416         *
1417         * @throws ParseException If the parameters couldn't be parsed to an
1418         *                        OpenID Connect authentication request.
1419         */
1420        public static AuthenticationRequest parse(final Map<String,List<String>> params)
1421                throws ParseException {
1422
1423                return parse(null, params);
1424        }
1425
1426
1427        /**
1428         * Parses an OpenID Connect authentication request from the specified
1429         * URI and query parameters.
1430         *
1431         * <p>Example parameters:
1432         *
1433         * <pre>
1434         * response_type = token id_token
1435         * client_id     = s6BhdRkqt3
1436         * redirect_uri  = https://client.example.com/cb
1437         * scope         = openid profile
1438         * state         = af0ifjsldkj
1439         * nonce         = -0S6_WzA2Mj
1440         * </pre>
1441         *
1442         * @param uri    The URI of the OAuth 2.0 authorisation endpoint. May
1443         *               be {@code null} if the {@link #toHTTPRequest} method
1444         *               will not be used.
1445         * @param params The parameters. Must not be {@code null}.
1446         *
1447         * @return The OpenID Connect authentication request.
1448         *
1449         * @throws ParseException If the parameters couldn't be parsed to an
1450         *                        OpenID Connect authentication request.
1451         */
1452        public static AuthenticationRequest parse(final URI uri, final Map<String,List<String>> params)
1453                throws ParseException {
1454
1455                // Parse and validate the core OAuth 2.0 autz request params in 
1456                // the context of OIDC
1457                AuthorizationRequest ar = AuthorizationRequest.parse(uri, params);
1458                
1459                Nonce nonce = Nonce.parse(MultivaluedMapUtils.getFirstValue(params, "nonce"));
1460                
1461                if (! ar.specifiesRequestObject()) {
1462                        
1463                        // Required params if no JAR is present
1464                        
1465                        if (ar.getRedirectionURI() == null) {
1466                                String msg = "Missing \"redirect_uri\" parameter";
1467                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1468                                        ar.getClientID(), null, ar.impliedResponseMode(), ar.getState());
1469                        }
1470                        
1471                        if (ar.getScope() == null) {
1472                                String msg = "Missing \"scope\" parameter";
1473                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1474                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1475                        }
1476                        
1477                        // Nonce required in the implicit and hybrid flows
1478                        if (nonce == null && (ar.getResponseType().impliesImplicitFlow() || ar.getResponseType().impliesHybridFlow())) {
1479                                String msg = "Missing \"nonce\" parameter: Required in the implicit and hybrid flows";
1480                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1481                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1482                        }
1483                }
1484                
1485                // Check if present (not in JAR)
1486                if (ar.getResponseType() != null) {
1487                        try {
1488                                OIDCResponseTypeValidator.validate(ar.getResponseType());
1489                        } catch (IllegalArgumentException e) {
1490                                String msg = "Unsupported \"response_type\" parameter: " + e.getMessage();
1491                                throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg),
1492                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1493                        }
1494                }
1495                
1496                // Check if present (not in JAR)
1497                if (ar.getScope() != null && ! ar.getScope().contains(OIDCScopeValue.OPENID)) {
1498                        String msg = "The scope must include an \"openid\" value";
1499                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1500                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1501                }
1502                
1503                Display display = null;
1504
1505                if (params.containsKey("display")) {
1506                        try {
1507                                display = Display.parse(MultivaluedMapUtils.getFirstValue(params, "display"));
1508
1509                        } catch (ParseException e) {
1510                                String msg = "Invalid \"display\" parameter: " + e.getMessage();
1511                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1512                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1513                        }
1514                }
1515
1516
1517                String v = MultivaluedMapUtils.getFirstValue(params, "max_age");
1518
1519                int maxAge = -1;
1520
1521                if (StringUtils.isNotBlank(v)) {
1522
1523                        try {
1524                                maxAge = Integer.parseInt(v);
1525
1526                        } catch (NumberFormatException e) {
1527                                String msg = "Invalid \"max_age\" parameter: " + v;
1528                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1529                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1530                        }
1531                }
1532
1533
1534                v = MultivaluedMapUtils.getFirstValue(params, "ui_locales");
1535
1536                List<LangTag> uiLocales = null;
1537
1538                if (StringUtils.isNotBlank(v)) {
1539
1540                        uiLocales = new LinkedList<>();
1541
1542                        StringTokenizer st = new StringTokenizer(v, " ");
1543
1544                        while (st.hasMoreTokens()) {
1545
1546                                try {
1547                                        uiLocales.add(LangTag.parse(st.nextToken()));
1548
1549                                } catch (LangTagException e) {
1550                                        String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage();
1551                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1552                                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1553                                }
1554                        }
1555                }
1556
1557
1558                v = MultivaluedMapUtils.getFirstValue(params, "claims_locales");
1559
1560                List<LangTag> claimsLocales = null;
1561
1562                if (StringUtils.isNotBlank(v)) {
1563
1564                        claimsLocales = new LinkedList<>();
1565
1566                        StringTokenizer st = new StringTokenizer(v, " ");
1567
1568                        while (st.hasMoreTokens()) {
1569
1570                                try {
1571                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1572
1573                                } catch (LangTagException e) {
1574                                        String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage();
1575                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1576                                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1577                                }
1578                        }
1579                }
1580
1581
1582                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1583                
1584                JWT idTokenHint = null;
1585                
1586                if (StringUtils.isNotBlank(v)) {
1587                
1588                        try {
1589                                idTokenHint = JWTParser.parse(v);
1590                                
1591                        } catch (java.text.ParseException e) {
1592                                String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage();
1593                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1594                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1595                        }
1596                }
1597
1598                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1599
1600
1601                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1602
1603                List<ACR> acrValues = null;
1604
1605                if (StringUtils.isNotBlank(v)) {
1606
1607                        acrValues = new LinkedList<>();
1608
1609                        StringTokenizer st = new StringTokenizer(v, " ");
1610
1611                        while (st.hasMoreTokens()) {
1612
1613                                acrValues.add(new ACR(st.nextToken()));
1614                        }
1615                }
1616
1617
1618                v = MultivaluedMapUtils.getFirstValue(params, "claims");
1619
1620                ClaimsRequest claims = null;
1621
1622                if (StringUtils.isNotBlank(v)) {
1623
1624                        JSONObject jsonObject;
1625
1626                        try {
1627                                jsonObject = JSONObjectUtils.parse(v);
1628
1629                        } catch (ParseException e) {
1630                                String msg = "Invalid \"claims\" parameter: " + e.getMessage();
1631                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1632                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1633                        }
1634                        
1635                        try {
1636                                claims = ClaimsRequest.parse(jsonObject);
1637                        } catch (ParseException e) {
1638                                throw new ParseException(e.getMessage(), e.getErrorObject(),
1639                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1640                        }
1641                }
1642                
1643                String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose");
1644                
1645                if (purpose != null && (purpose.length() < PURPOSE_MIN_LENGTH || purpose.length() > PURPOSE_MAX_LENGTH)) {
1646                        String msg = "Invalid \"purpose\" parameter: Must not be shorter than " + PURPOSE_MIN_LENGTH + " and longer than " + PURPOSE_MAX_LENGTH + " characters";
1647                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1648                                ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1649                }
1650
1651                // Parse additional custom parameters
1652                Map<String,List<String>> customParams = null;
1653
1654                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1655
1656                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1657                                // We have a custom parameter
1658                                if (customParams == null) {
1659                                        customParams = new HashMap<>();
1660                                }
1661                                customParams.put(p.getKey(), p.getValue());
1662                        }
1663                }
1664
1665
1666                return new AuthenticationRequest(
1667                        uri, ar.getResponseType(), ar.getResponseMode(), ar.getScope(), ar.getClientID(), ar.getRedirectionURI(), ar.getState(), nonce,
1668                        display, ar.getPrompt(), maxAge, uiLocales, claimsLocales,
1669                        idTokenHint, loginHint, acrValues, claims, purpose,
1670                        ar.getRequestObject(), ar.getRequestURI(),
1671                        ar.getCodeChallenge(), ar.getCodeChallengeMethod(),
1672                        ar.getResources(),
1673                        ar.includeGrantedScopes(),
1674                        customParams);
1675        }
1676        
1677        
1678        /**
1679         * Parses an OpenID Connect authentication request from the specified
1680         * URI query string.
1681         *
1682         * <p>Example URI query string:
1683         *
1684         * <pre>
1685         * response_type=token%20id_token
1686         * &amp;client_id=s6BhdRkqt3
1687         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1688         * &amp;scope=openid%20profile
1689         * &amp;state=af0ifjsldkj
1690         * &amp;nonce=n-0S6_WzA2Mj
1691         * </pre>
1692         *
1693         * @param query The URI query string. Must not be {@code null}.
1694         *
1695         * @return The OpenID Connect authentication request.
1696         *
1697         * @throws ParseException If the query string couldn't be parsed to an 
1698         *                        OpenID Connect authentication request.
1699         */
1700        public static AuthenticationRequest parse(final String query)
1701                throws ParseException {
1702        
1703                return parse(null, URLUtils.parseParameters(query));
1704        }
1705
1706
1707        /**
1708         * Parses an OpenID Connect authentication request from the specified
1709         * URI query string.
1710         *
1711         * <p>Example URI query string:
1712         *
1713         * <pre>
1714         * response_type=token%20id_token
1715         * &amp;client_id=s6BhdRkqt3
1716         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1717         * &amp;scope=openid%20profile
1718         * &amp;state=af0ifjsldkj
1719         * &amp;nonce=n-0S6_WzA2Mj
1720         * </pre>
1721         *
1722         * @param uri   The URI of the OAuth 2.0 authorisation endpoint. May be
1723         *              {@code null} if the {@link #toHTTPRequest} method will
1724         *              not be used.
1725         * @param query The URI query string. Must not be {@code null}.
1726         *
1727         * @return The OpenID Connect authentication request.
1728         *
1729         * @throws ParseException If the query string couldn't be parsed to an
1730         *                        OpenID Connect authentication request.
1731         */
1732        public static AuthenticationRequest parse(final URI uri, final String query)
1733                throws ParseException {
1734
1735                return parse(uri, URLUtils.parseParameters(query));
1736        }
1737
1738
1739        /**
1740         * Parses an OpenID Connect authentication request from the specified
1741         * URI.
1742         *
1743         * <p>Example URI:
1744         *
1745         * <pre>
1746         * https://server.example.com/authorize?
1747         * response_type=token%20id_token
1748         * &amp;client_id=s6BhdRkqt3
1749         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1750         * &amp;scope=openid%20profile
1751         * &amp;state=af0ifjsldkj
1752         * &amp;nonce=n-0S6_WzA2Mj
1753         * </pre>
1754         *
1755         * @param uri The URI. Must not be {@code null}.
1756         *
1757         * @return The OpenID Connect authentication request.
1758         *
1759         * @throws ParseException If the query string couldn't be parsed to an
1760         *                        OpenID Connect authentication request.
1761         */
1762        public static AuthenticationRequest parse(final URI uri)
1763                throws ParseException {
1764
1765                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1766        }
1767        
1768        
1769        /**
1770         * Parses an authentication request from the specified HTTP GET or HTTP
1771         * POST request.
1772         *
1773         * <p>Example HTTP request (GET):
1774         *
1775         * <pre>
1776         * https://server.example.com/op/authorize?
1777         * response_type=code%20id_token
1778         * &amp;client_id=s6BhdRkqt3
1779         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1780         * &amp;scope=openid
1781         * &amp;nonce=n-0S6_WzA2Mj
1782         * &amp;state=af0ifjsldkj
1783         * </pre>
1784         *
1785         * @param httpRequest The HTTP request. Must not be {@code null}.
1786         *
1787         * @return The OpenID Connect authentication request.
1788         *
1789         * @throws ParseException If the HTTP request couldn't be parsed to an 
1790         *                        OpenID Connect authentication request.
1791         */
1792        public static AuthenticationRequest parse(final HTTPRequest httpRequest)
1793                throws ParseException {
1794                
1795                String query = httpRequest.getQuery();
1796                
1797                if (query == null)
1798                        throw new ParseException("Missing URI query string");
1799
1800                URI endpointURI;
1801
1802                try {
1803                        endpointURI = httpRequest.getURL().toURI();
1804
1805                } catch (URISyntaxException e) {
1806
1807                        throw new ParseException(e.getMessage(), e);
1808                }
1809                
1810                return parse(endpointURI, query);
1811        }
1812}