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