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-17
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 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 builder.
404                 *
405                 * @param requestObject The request object. Must not be
406                 *                      {@code null}.
407                 */
408                public Builder(final JWT requestObject) {
409                        
410                        if (requestObject == null)
411                                throw new IllegalArgumentException("The request object must not be null");
412
413                        this.requestObject = requestObject;
414                }
415
416
417                /**
418                 * Creates a new JWT secured OpenID Connect authentication
419                 * request builder.
420                 *
421                 * @param requestURI The request object URI. Must not be
422                 *                   {@code null}.
423                 */
424                public Builder(final URI requestURI) {
425                        
426                        if (requestURI == null)
427                                throw new IllegalArgumentException("The request URI must not be null");
428
429                        this.requestURI = requestURI;
430                }
431                
432                
433                /**
434                 * Creates a new OpenID Connect authentication request builder
435                 * from the specified request.
436                 *
437                 * @param request The OpenID Connect authentication request.
438                 *                Must not be {@code null}.
439                 */
440                public Builder(final AuthenticationRequest request) {
441                        
442                        uri = request.getEndpointURI();
443                        rt = request.getResponseType();
444                        clientID = request.getClientID();
445                        redirectURI = request.getRedirectionURI();
446                        scope = request.getScope();
447                        state = request.getState();
448                        nonce = request.getNonce();
449                        display = request.getDisplay();
450                        prompt = request.getPrompt();
451                        maxAge = request.getMaxAge();
452                        uiLocales = request.getUILocales();
453                        claimsLocales = request.getClaimsLocales();
454                        idTokenHint = request.getIDTokenHint();
455                        loginHint = request.getLoginHint();
456                        acrValues = request.getACRValues();
457                        claims = request.getClaims();
458                        purpose = request.getPurpose();
459                        requestObject = request.getRequestObject();
460                        requestURI = request.getRequestURI();
461                        rm = request.getResponseMode();
462                        codeChallenge = request.getCodeChallenge();
463                        codeChallengeMethod = request.getCodeChallengeMethod();
464                        resources = request.getResources();
465                        includeGrantedScopes = request.includeGrantedScopes();
466                        customParams.putAll(request.getCustomParameters());
467                }
468                
469                
470                /**
471                 * Sets the response type. Corresponds to the
472                 * {@code response_type} parameter.
473                 *
474                 * @param rt The response type. Must not be {@code null}.
475                 *
476                 * @return This builder.
477                 */
478                public Builder responseType(final ResponseType rt) {
479                        
480                        if (rt == null)
481                                throw new IllegalArgumentException("The response type must not be null");
482                        
483                        this.rt = rt;
484                        return this;
485                }
486                
487                
488                /**
489                 * Sets the scope. Corresponds to the {@code scope} parameter.
490                 *
491                 * @param scope The scope. Must not be {@code null}.
492                 *
493                 * @return This builder.
494                 */
495                public Builder scope(final Scope scope) {
496                        
497                        if (scope == null)
498                                throw new IllegalArgumentException("The scope must not be null");
499                        
500                        if (! scope.contains(OIDCScopeValue.OPENID))
501                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
502                        
503                        this.scope = scope;
504                        return this;
505                }
506                
507                
508                /**
509                 * Sets the client identifier. Corresponds to the
510                 * {@code client_id} parameter.
511                 *
512                 * @param clientID The client identifier. Must not be
513                 *                 {@code null}.
514                 *
515                 * @return This builder.
516                 */
517                public Builder clientID(final ClientID clientID) {
518                        
519                        if (clientID == null)
520                                throw new IllegalArgumentException("The client ID must not be null");
521                        
522                        this.clientID = clientID;
523                        return this;
524                }
525                
526                
527                /**
528                 * Sets the redirection URI. Corresponds to the
529                 * {@code redirection_uri} parameter.
530                 *
531                 * @param redirectURI The redirection URI. Must not be
532                 *                    {@code null}.
533                 *
534                 * @return This builder.
535                 */
536                public Builder redirectionURI(final URI redirectURI) {
537                        
538                        if (redirectURI == null)
539                                throw new IllegalArgumentException("The redirection URI must not be null");
540                        
541                        this.redirectURI = redirectURI;
542                        return this;
543                }
544
545
546                /**
547                 * Sets the state. Corresponds to the recommended {@code state}
548                 * parameter.
549                 *
550                 * @param state The state, {@code null} if not specified.
551                 *
552                 * @return This builder.
553                 */
554                public Builder state(final State state) {
555
556                        this.state = state;
557                        return this;
558                }
559
560
561                /**
562                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
563                 * request is intended.
564                 *
565                 * @param uri The endpoint URI, {@code null} if not specified.
566                 *
567                 * @return This builder.
568                 */
569                public Builder endpointURI(final URI uri) {
570
571                        this.uri = uri;
572                        return this;
573                }
574
575
576                /**
577                 * Sets the nonce. Corresponds to the conditionally optional
578                 * {@code nonce} parameter.
579                 *
580                 * @param nonce The nonce, {@code null} if not specified.
581                 *
582                 * @return This builder.
583                 */
584                public Builder nonce(final Nonce nonce) {
585
586                        this.nonce = nonce;
587                        return this;
588                }
589
590
591                /**
592                 * Sets the requested display type. Corresponds to the optional
593                 * {@code display} parameter.
594                 *
595                 * @param display The requested display type, {@code null} if
596                 *                not specified.
597                 *
598                 * @return This builder.
599                 */
600                public Builder display(final Display display) {
601
602                        this.display = display;
603                        return this;
604                }
605
606
607                /**
608                 * Sets the requested prompt. Corresponds to the optional
609                 * {@code prompt} parameter.
610                 *
611                 * @param prompt The requested prompt, {@code null} if not
612                 *               specified.
613                 *
614                 * @return This builder.
615                 */
616                public Builder prompt(final Prompt prompt) {
617
618                        this.prompt = prompt;
619                        return this;
620                }
621
622
623                /**
624                 * Sets the required maximum authentication age. Corresponds to
625                 * the optional {@code max_age} parameter.
626                 *
627                 * @param maxAge The maximum authentication age, in seconds; 0
628                 *               if not specified.
629                 *
630                 * @return This builder.
631                 */
632                public Builder maxAge(final int maxAge) {
633
634                        this.maxAge = maxAge;
635                        return this;
636                }
637
638
639                /**
640                 * Sets the end-user's preferred languages and scripts for the
641                 * user interface, ordered by preference. Corresponds to the
642                 * optional {@code ui_locales} parameter.
643                 *
644                 * @param uiLocales The preferred UI locales, {@code null} if
645                 *                  not specified.
646                 *
647                 * @return This builder.
648                 */
649                public Builder uiLocales(final List<LangTag> uiLocales) {
650
651                        this.uiLocales = uiLocales;
652                        return this;
653                }
654
655
656                /**
657                 * Sets the end-user's preferred languages and scripts for the
658                 * claims being returned, ordered by preference. Corresponds to
659                 * the optional {@code claims_locales} parameter.
660                 *
661                 * @param claimsLocales The preferred claims locales,
662                 *                      {@code null} if not specified.
663                 *
664                 * @return This builder.
665                 */
666                public Builder claimsLocales(final List<LangTag> claimsLocales) {
667
668                        this.claimsLocales = claimsLocales;
669                        return this;
670                }
671
672
673                /**
674                 * Sets the ID Token hint. Corresponds to the conditionally
675                 * optional {@code id_token_hint} parameter.
676                 *
677                 * @param idTokenHint The ID Token hint, {@code null} if not
678                 *                    specified.
679                 *
680                 * @return This builder.
681                 */
682                public Builder idTokenHint(final JWT idTokenHint) {
683
684                        this.idTokenHint = idTokenHint;
685                        return this;
686                }
687
688
689                /**
690                 * Sets the login hint. Corresponds to the optional
691                 * {@code login_hint} parameter.
692                 *
693                 * @param loginHint The login hint, {@code null} if not
694                 *                  specified.
695                 *
696                 * @return This builder.
697                 */
698                public Builder loginHint(final String loginHint) {
699
700                        this.loginHint = loginHint;
701                        return this;
702                }
703
704
705                /**
706                 * Sets the requested Authentication Context Class Reference
707                 * values. Corresponds to the optional {@code acr_values}
708                 * parameter.
709                 *
710                 * @param acrValues The requested ACR values, {@code null} if
711                 *                  not specified.
712                 *
713                 * @return This builder.
714                 */
715                public Builder acrValues(final List<ACR> acrValues) {
716
717                        this.acrValues = acrValues;
718                        return this;
719                }
720
721
722                /**
723                 * Sets the individual claims to be returned. Corresponds to
724                 * the optional {@code claims} parameter.
725                 *
726                 * @param claims The individual claims to be returned,
727                 *               {@code null} if not specified.
728                 *
729                 * @return This builder.
730                 */
731                public Builder claims(final ClaimsRequest claims) {
732
733                        this.claims = claims;
734                        return this;
735                }
736                
737                
738                /**
739                 * Sets the transaction specific purpose. Corresponds to the
740                 * optional {@code purpose} parameter.
741                 *
742                 * @param purpose The purpose, {@code null} if not specified.
743                 *
744                 * @return This builder.
745                 */
746                public Builder purpose(final String purpose) {
747                        
748                        this.purpose = purpose;
749                        return this;
750                }
751
752
753                /**
754                 * Sets the request object. Corresponds to the optional
755                 * {@code request} parameter. Must not be specified together
756                 * with a request object URI.
757                 *
758                 * @param requestObject The request object, {@code null} if not
759                 *                      specified.
760                 *
761                 * @return This builder.
762                 */
763                public Builder requestObject(final JWT requestObject) {
764
765                        this.requestObject = requestObject;
766                        return this;
767                }
768
769
770                /**
771                 * Sets the request object URI. Corresponds to the optional
772                 * {@code request_uri} parameter. Must not be specified
773                 * together with a request object.
774                 *
775                 * @param requestURI The request object URI, {@code null} if
776                 *                   not specified.
777                 *
778                 * @return This builder.
779                 */
780                public Builder requestURI(final URI requestURI) {
781
782                        this.requestURI = requestURI;
783                        return this;
784                }
785
786
787                /**
788                 * Sets the response mode. Corresponds to the optional
789                 * {@code response_mode} parameter. Use of this parameter is
790                 * not recommended unless a non-default response mode is
791                 * requested (e.g. form_post).
792                 *
793                 * @param rm The response mode, {@code null} if not specified.
794                 *
795                 * @return This builder.
796                 */
797                public Builder responseMode(final ResponseMode rm) {
798
799                        this.rm = rm;
800                        return this;
801                }
802                
803                
804                /**
805                 * Sets the code challenge for Proof Key for Code Exchange
806                 * (PKCE) by public OAuth clients.
807                 *
808                 * @param codeChallenge       The code challenge, {@code null}
809                 *                            if not specified.
810                 * @param codeChallengeMethod The code challenge method,
811                 *                            {@code null} if not specified.
812                 *
813                 * @return This builder.
814                 */
815                @Deprecated
816                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
817                        
818                        this.codeChallenge = codeChallenge;
819                        this.codeChallengeMethod = codeChallengeMethod;
820                        return this;
821                }
822                
823                
824                /**
825                 * Sets the code challenge for Proof Key for Code Exchange
826                 * (PKCE) by public OAuth clients.
827                 *
828                 * @param codeVerifier        The code verifier to use to
829                 *                            compute the code challenge,
830                 *                            {@code null} if PKCE is not
831                 *                            specified.
832                 * @param codeChallengeMethod The code challenge method,
833                 *                            {@code null} if not specified.
834                 *                            Defaults to
835                 *                            {@link CodeChallengeMethod#PLAIN}
836                 *                            if a code verifier is specified.
837                 *
838                 * @return This builder.
839                 */
840                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
841                        
842                        if (codeVerifier != null) {
843                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
844                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
845                                this.codeChallengeMethod = method;
846                        } else {
847                                this.codeChallenge = null;
848                                this.codeChallengeMethod = null;
849                        }
850                        return this;
851                }
852                
853                
854                /**
855                 * Sets the resource server URI(s).
856                 *
857                 * @param resources The resource URI(s), {@code null} if not
858                 *                  specified.
859                 *
860                 * @return This builder.
861                 */
862                public Builder resources(final URI ... resources) {
863                        if (resources != null) {
864                                this.resources = Arrays.asList(resources);
865                        } else {
866                                this.resources = null;
867                        }
868                        return this;
869                }
870                
871                
872                /**
873                 * Requests incremental authorisation.
874                 *
875                 * @param includeGrantedScopes {@code true} to request
876                 *                             incremental authorisation.
877                 *
878                 * @return This builder.
879                 */
880                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
881                        
882                        this.includeGrantedScopes = includeGrantedScopes;
883                        return this;
884                }
885                
886                
887                /**
888                 * Sets a custom parameter.
889                 *
890                 * @param name   The parameter name. Must not be {@code null}.
891                 * @param values The parameter values, {@code null} if not
892                 *               specified.
893                 *
894                 * @return This builder.
895                 */
896                public Builder customParameter(final String name, final String ... values) {
897                        
898                        if (values == null || values.length == 0) {
899                                customParams.remove(name);
900                        } else {
901                                customParams.put(name, Arrays.asList(values));
902                        }
903                        
904                        return this;
905                }
906
907
908                /**
909                 * Builds a new authentication request.
910                 *
911                 * @return The authentication request.
912                 */
913                public AuthenticationRequest build() {
914
915                        try {
916                                return new AuthenticationRequest(
917                                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
918                                        display, prompt, maxAge, uiLocales, claimsLocales,
919                                        idTokenHint, loginHint, acrValues, claims,
920                                        purpose,
921                                        requestObject, requestURI,
922                                        codeChallenge, codeChallengeMethod,
923                                        resources,
924                                        includeGrantedScopes,
925                                        customParams);
926
927                        } catch (IllegalArgumentException e) {
928                                throw new IllegalStateException(e.getMessage(), e);
929                        }
930                }
931        }
932        
933        
934        /**
935         * Creates a new minimal OpenID Connect authentication request.
936         *
937         * @param uri         The URI of the OAuth 2.0 authorisation endpoint.
938         *                    May be {@code null} if the {@link #toHTTPRequest}
939         *                    method will not be used.
940         * @param rt          The response type. Corresponds to the 
941         *                    {@code response_type} parameter. Must specify a
942         *                    valid OpenID Connect response type. Must not be
943         *                    {@code null}.
944         * @param scope       The request scope. Corresponds to the
945         *                    {@code scope} parameter. Must contain an
946         *                    {@link OIDCScopeValue#OPENID openid value}. Must
947         *                    not be {@code null}.
948         * @param clientID    The client identifier. Corresponds to the
949         *                    {@code client_id} parameter. Must not be 
950         *                    {@code null}.
951         * @param redirectURI The redirection URI. Corresponds to the
952         *                    {@code redirect_uri} parameter. Must not be 
953         *                    {@code null}.
954         * @param state       The state. Corresponds to the {@code state}
955         *                    parameter. May be {@code null}.
956         * @param nonce       The nonce. Corresponds to the {@code nonce} 
957         *                    parameter. May be {@code null} for code flow.
958         */
959        public AuthenticationRequest(final URI uri,
960                                     final ResponseType rt,
961                                     final Scope scope,
962                                     final ClientID clientID,
963                                     final URI redirectURI,
964                                     final State state,
965                                     final Nonce nonce) {
966
967                // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 
968                // idTokenHint, loginHint, acrValues, claims, purpose
969                // codeChallenge, codeChallengeMethod
970                this(uri, rt, null, scope, clientID, redirectURI, state, nonce,
971                        null, null, -1, null, null,
972                        null, null, null, null, null,
973                        null, null,
974                        null, null,
975                        null, false, null);
976        }
977
978
979        /**
980         * Creates a new OpenID Connect authentication request with extension
981         * and custom parameters.
982         *
983         * @param uri                  The URI of the OAuth 2.0 authorisation
984         *                             endpoint. May be {@code null} if the
985         *                             {@link #toHTTPRequest} method will not
986         *                             be used.
987         * @param rt                   The response type set. Corresponds to
988         *                             the {@code response_type} parameter.
989         *                             Must specify a valid OpenID Connect
990         *                             response type. Must not be {@code null}.
991         * @param rm                   The response mode. Corresponds to the
992         *                             optional {@code response_mode}
993         *                             parameter. Use of this parameter is not
994         *                             recommended unless a non-default
995         *                             response mode is requested (e.g.
996         *                             form_post).
997         * @param scope                The request scope. Corresponds to the
998         *                             {@code scope} parameter. Must contain an
999         *                             {@link OIDCScopeValue#OPENID openid
1000         *                             value}. Must not be {@code null}.
1001         * @param clientID             The client identifier. Corresponds to
1002         *                             the {@code client_id} parameter. Must
1003         *                             not be {@code null}.
1004         * @param redirectURI          The redirection URI. Corresponds to the
1005         *                             {@code redirect_uri} parameter. Must not
1006         *                             be {@code null} unless set by means of
1007         *                             the optional {@code request_object} /
1008         *                             {@code request_uri} parameter.
1009         * @param state                The state. Corresponds to the
1010         *                             recommended {@code state} parameter.
1011         *                             {@code null} if not specified.
1012         * @param nonce                The nonce. Corresponds to the
1013         *                             {@code nonce} parameter. May be
1014         *                             {@code null} for code flow.
1015         * @param display              The requested display type. Corresponds
1016         *                             to the optional {@code display}
1017         *                             parameter.
1018         *                             {@code null} if not specified.
1019         * @param prompt               The requested prompt. Corresponds to the
1020         *                             optional {@code prompt} parameter.
1021         *                             {@code null} if not specified.
1022         * @param maxAge               The required maximum authentication age,
1023         *                             in seconds. Corresponds to the optional
1024         *                             {@code max_age} parameter. -1 if not
1025         *                             specified, zero implies
1026         *                             {@code prompt=login}.
1027         * @param uiLocales            The preferred languages and scripts for
1028         *                             the user interface. Corresponds to the
1029         *                             optional {@code ui_locales} parameter.
1030         *                             {@code null} if not specified.
1031         * @param claimsLocales        The preferred languages and scripts for
1032         *                             claims being returned. Corresponds to
1033         *                             the optional {@code claims_locales}
1034         *                             parameter. {@code null} if not
1035         *                             specified.
1036         * @param idTokenHint          The ID Token hint. Corresponds to the
1037         *                             optional {@code id_token_hint}
1038         *                             parameter. {@code null} if not
1039         *                             specified.
1040         * @param loginHint            The login hint. Corresponds to the
1041         *                             optional {@code login_hint} parameter.
1042         *                             {@code null} if not specified.
1043         * @param acrValues            The requested Authentication Context
1044         *                             Class Reference values. Corresponds to
1045         *                             the optional {@code acr_values}
1046         *                             parameter. {@code null} if not
1047         *                             specified.
1048         * @param claims               The individual claims to be returned.
1049         *                             Corresponds to the optional
1050         *                             {@code claims} parameter. {@code null}
1051         *                             if not specified.
1052         * @param purpose              The transaction specific purpose,
1053         *                             {@code null} if not specified.
1054         * @param requestObject        The request object. Corresponds to the
1055         *                             optional {@code request} parameter. Must
1056         *                             not be specified together with a request
1057         *                             object URI. {@code null} if not
1058         *                             specified.
1059         * @param requestURI           The request object URI. Corresponds to
1060         *                             the optional {@code request_uri}
1061         *                             parameter. Must not be specified
1062         *                             together with a request object.
1063         *                             {@code null} if not specified.
1064         * @param codeChallenge        The code challenge for PKCE,
1065         *                             {@code null} if not specified.
1066         * @param codeChallengeMethod  The code challenge method for PKCE,
1067         *                             {@code null} if not specified.
1068         * @param resources            The resource URI(s), {@code null} if not
1069         *                             specified.
1070         * @param includeGrantedScopes {@code true} to request incremental
1071         *                             authorisation.
1072         * @param customParams         Additional custom parameters, empty map
1073         *                             or {@code null} if none.
1074         */
1075        public AuthenticationRequest(final URI uri,
1076                                     final ResponseType rt,
1077                                     final ResponseMode rm,
1078                                     final Scope scope,
1079                                     final ClientID clientID,
1080                                     final URI redirectURI,
1081                                     final State state,
1082                                     final Nonce nonce,
1083                                     final Display display,
1084                                     final Prompt prompt,
1085                                     final int maxAge,
1086                                     final List<LangTag> uiLocales,
1087                                     final List<LangTag> claimsLocales,
1088                                     final JWT idTokenHint,
1089                                     final String loginHint,
1090                                     final List<ACR> acrValues,
1091                                     final ClaimsRequest claims,
1092                                     final String purpose,
1093                                     final JWT requestObject,
1094                                     final URI requestURI,
1095                                     final CodeChallenge codeChallenge,
1096                                     final CodeChallengeMethod codeChallengeMethod,
1097                                     final List<URI> resources,
1098                                     final boolean includeGrantedScopes,
1099                                     final Map<String,List<String>> customParams) {
1100
1101                super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, requestObject, requestURI, prompt, customParams);
1102                
1103                if (! specifiesRequestObject()) {
1104                        
1105                        // Check parameters required by OpenID Connect if no JAR
1106                        
1107                        if (redirectURI == null)
1108                                throw new IllegalArgumentException("The redirection URI must not be null");
1109                        
1110                        OIDCResponseTypeValidator.validate(rt);
1111                        
1112                        if (scope == null)
1113                                throw new IllegalArgumentException("The scope must not be null");
1114                        
1115                        if (!scope.contains(OIDCScopeValue.OPENID))
1116                                throw new IllegalArgumentException("The scope must include an \"openid\" value");
1117                        
1118                        // Nonce required in the implicit and hybrid flows
1119                        if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow()))
1120                                throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow");
1121                }
1122                
1123                this.nonce = nonce;
1124                
1125                // Optional parameters
1126                this.display = display;
1127                this.maxAge = maxAge;
1128
1129                if (uiLocales != null)
1130                        this.uiLocales = Collections.unmodifiableList(uiLocales);
1131                else
1132                        this.uiLocales = null;
1133
1134                if (claimsLocales != null)
1135                        this.claimsLocales = Collections.unmodifiableList(claimsLocales);
1136                else
1137                        this.claimsLocales = null;
1138
1139                this.idTokenHint = idTokenHint;
1140                this.loginHint = loginHint;
1141
1142                if (acrValues != null)
1143                        this.acrValues = Collections.unmodifiableList(acrValues);
1144                else
1145                        this.acrValues = null;
1146
1147                this.claims = claims;
1148                
1149                if (purpose != null) {
1150                        if (purpose.length() < PURPOSE_MIN_LENGTH) {
1151                                throw new IllegalArgumentException("The purpose must not be shorter than " + PURPOSE_MIN_LENGTH + " characters");
1152                        }
1153                        if (purpose.length() > PURPOSE_MAX_LENGTH) {
1154                                throw new IllegalArgumentException("The purpose must not be longer than " + PURPOSE_MAX_LENGTH +" characters");
1155                        }
1156                }
1157                
1158                this.purpose = purpose;
1159        }
1160
1161
1162        /**
1163         * Returns the registered (standard) OpenID Connect authentication
1164         * request parameter names.
1165         *
1166         * @return The registered OpenID Connect authentication request
1167         *         parameter names, as a unmodifiable set.
1168         */
1169        public static Set<String> getRegisteredParameterNames() {
1170
1171                return REGISTERED_PARAMETER_NAMES;
1172        }
1173        
1174        
1175        /**
1176         * Gets the nonce. Corresponds to the conditionally optional 
1177         * {@code nonce} parameter.
1178         *
1179         * @return The nonce, {@code null} if not specified.
1180         */
1181        public Nonce getNonce() {
1182        
1183                return nonce;
1184        }
1185        
1186        
1187        /**
1188         * Gets the requested display type. Corresponds to the optional
1189         * {@code display} parameter.
1190         *
1191         * @return The requested display type, {@code null} if not specified.
1192         */
1193        public Display getDisplay() {
1194        
1195                return display;
1196        }
1197        
1198        
1199        /**
1200         * Gets the required maximum authentication age. Corresponds to the
1201         * optional {@code max_age} parameter.
1202         *
1203         * @return The maximum authentication age, in seconds; -1 if not
1204         *         specified, zero implies {@code prompt=login}.
1205         */
1206        public int getMaxAge() {
1207        
1208                return maxAge;
1209        }
1210
1211
1212        /**
1213         * Gets the end-user's preferred languages and scripts for the user
1214         * interface, ordered by preference. Corresponds to the optional
1215         * {@code ui_locales} parameter.
1216         *
1217         * @return The preferred UI locales, {@code null} if not specified.
1218         */
1219        public List<LangTag> getUILocales() {
1220
1221                return uiLocales;
1222        }
1223
1224
1225        /**
1226         * Gets the end-user's preferred languages and scripts for the claims
1227         * being returned, ordered by preference. Corresponds to the optional
1228         * {@code claims_locales} parameter.
1229         *
1230         * @return The preferred claims locales, {@code null} if not specified.
1231         */
1232        public List<LangTag> getClaimsLocales() {
1233
1234                return claimsLocales;
1235        }
1236
1237
1238        /**
1239         * Gets the ID Token hint. Corresponds to the conditionally optional 
1240         * {@code id_token_hint} parameter.
1241         *
1242         * @return The ID Token hint, {@code null} if not specified.
1243         */
1244        public JWT getIDTokenHint() {
1245        
1246                return idTokenHint;
1247        }
1248
1249
1250        /**
1251         * Gets the login hint. Corresponds to the optional {@code login_hint} 
1252         * parameter.
1253         *
1254         * @return The login hint, {@code null} if not specified.
1255         */
1256        public String getLoginHint() {
1257
1258                return loginHint;
1259        }
1260
1261
1262        /**
1263         * Gets the requested Authentication Context Class Reference values.
1264         * Corresponds to the optional {@code acr_values} parameter.
1265         *
1266         * @return The requested ACR values, {@code null} if not specified.
1267         */
1268        public List<ACR> getACRValues() {
1269
1270                return acrValues;
1271        }
1272
1273
1274        /**
1275         * Gets the individual claims to be returned. Corresponds to the 
1276         * optional {@code claims} parameter.
1277         *
1278         * @return The individual claims to be returned, {@code null} if not
1279         *         specified.
1280         */
1281        public ClaimsRequest getClaims() {
1282
1283                return claims;
1284        }
1285        
1286        
1287        /**
1288         * Gets the transaction specific purpose. Corresponds to the optional
1289         * {@code purpose} parameter.
1290         *
1291         * @return The purpose, {@code null} if not specified.
1292         */
1293        public String getPurpose() {
1294                
1295                return purpose;
1296        }
1297
1298
1299        @Override
1300        public Map<String,List<String>> toParameters() {
1301
1302                Map <String,List<String>> params = super.toParameters();
1303                
1304                if (nonce != null)
1305                        params.put("nonce", Collections.singletonList(nonce.toString()));
1306                
1307                if (display != null)
1308                        params.put("display", Collections.singletonList(display.toString()));
1309
1310                if (maxAge >= 0)
1311                        params.put("max_age", Collections.singletonList("" + maxAge));
1312
1313                if (uiLocales != null) {
1314
1315                        StringBuilder sb = new StringBuilder();
1316
1317                        for (LangTag locale: uiLocales) {
1318
1319                                if (sb.length() > 0)
1320                                        sb.append(' ');
1321
1322                                sb.append(locale.toString());
1323                        }
1324
1325                        params.put("ui_locales", Collections.singletonList(sb.toString()));
1326                }
1327
1328                if (claimsLocales != null) {
1329
1330                        StringBuilder sb = new StringBuilder();
1331
1332                        for (LangTag locale: claimsLocales) {
1333
1334                                if (sb.length() > 0)
1335                                        sb.append(' ');
1336
1337                                sb.append(locale.toString());
1338                        }
1339
1340                        params.put("claims_locales", Collections.singletonList(sb.toString()));
1341                }
1342
1343                if (idTokenHint != null) {
1344                
1345                        try {
1346                                params.put("id_token_hint", Collections.singletonList(idTokenHint.serialize()));
1347                                
1348                        } catch (IllegalStateException e) {
1349                        
1350                                throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e);
1351                        }
1352                }
1353
1354                if (loginHint != null)
1355                        params.put("login_hint", Collections.singletonList(loginHint));
1356
1357                if (acrValues != null) {
1358
1359                        StringBuilder sb = new StringBuilder();
1360
1361                        for (ACR acr: acrValues) {
1362
1363                                if (sb.length() > 0)
1364                                        sb.append(' ');
1365
1366                                sb.append(acr.toString());
1367                        }
1368
1369                        params.put("acr_values", Collections.singletonList(sb.toString()));
1370                }
1371                        
1372
1373                if (claims != null)
1374                        params.put("claims", Collections.singletonList(claims.toJSONObject().toString()));
1375                
1376                if (purpose != null)
1377                        params.put("purpose", Collections.singletonList(purpose));
1378
1379                return params;
1380        }
1381        
1382        
1383        @Override
1384        public JWTClaimsSet toJWTClaimsSet() {
1385                
1386                JWTClaimsSet jwtClaimsSet = super.toJWTClaimsSet();
1387                
1388                if (jwtClaimsSet.getClaim("max_age") != null) {
1389                        // Convert max_age to number in JSON object
1390                        try {
1391                                String maxAgeString = jwtClaimsSet.getStringClaim("max_age");
1392                                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(jwtClaimsSet);
1393                                builder.claim("max_age", Integer.parseInt(maxAgeString));
1394                                return builder.build();
1395                        } catch (java.text.ParseException e) {
1396                                throw new SerializeException(e.getMessage());
1397                        }
1398                }
1399                
1400                return jwtClaimsSet;
1401        }
1402        
1403        
1404        /**
1405         * Parses an OpenID Connect authentication request from the specified
1406         * URI query parameters.
1407         *
1408         * <p>Example parameters:
1409         *
1410         * <pre>
1411         * response_type = token id_token
1412         * client_id     = s6BhdRkqt3
1413         * redirect_uri  = https://client.example.com/cb
1414         * scope         = openid profile
1415         * state         = af0ifjsldkj
1416         * nonce         = -0S6_WzA2Mj
1417         * </pre>
1418         *
1419         * @param params The parameters. Must not be {@code null}.
1420         *
1421         * @return The OpenID Connect authentication request.
1422         *
1423         * @throws ParseException If the parameters couldn't be parsed to an
1424         *                        OpenID Connect authentication request.
1425         */
1426        public static AuthenticationRequest parse(final Map<String,List<String>> params)
1427                throws ParseException {
1428
1429                return parse(null, params);
1430        }
1431
1432
1433        /**
1434         * Parses an OpenID Connect authentication request from the specified
1435         * URI and query parameters.
1436         *
1437         * <p>Example parameters:
1438         *
1439         * <pre>
1440         * response_type = token id_token
1441         * client_id     = s6BhdRkqt3
1442         * redirect_uri  = https://client.example.com/cb
1443         * scope         = openid profile
1444         * state         = af0ifjsldkj
1445         * nonce         = -0S6_WzA2Mj
1446         * </pre>
1447         *
1448         * @param uri    The URI of the OAuth 2.0 authorisation endpoint. May
1449         *               be {@code null} if the {@link #toHTTPRequest} method
1450         *               will not be used.
1451         * @param params The parameters. Must not be {@code null}.
1452         *
1453         * @return The OpenID Connect authentication request.
1454         *
1455         * @throws ParseException If the parameters couldn't be parsed to an
1456         *                        OpenID Connect authentication request.
1457         */
1458        public static AuthenticationRequest parse(final URI uri, final Map<String,List<String>> params)
1459                throws ParseException {
1460
1461                // Parse and validate the core OAuth 2.0 autz request params in 
1462                // the context of OIDC
1463                AuthorizationRequest ar = AuthorizationRequest.parse(uri, params);
1464                
1465                Nonce nonce = Nonce.parse(MultivaluedMapUtils.getFirstValue(params, "nonce"));
1466                
1467                if (! ar.specifiesRequestObject()) {
1468                        
1469                        // Required params if no JAR is present
1470                        
1471                        if (ar.getRedirectionURI() == null) {
1472                                String msg = "Missing \"redirect_uri\" parameter";
1473                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1474                                        ar.getClientID(), null, ar.impliedResponseMode(), ar.getState());
1475                        }
1476                        
1477                        if (ar.getScope() == null) {
1478                                String msg = "Missing \"scope\" parameter";
1479                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1480                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1481                        }
1482                        
1483                        // Nonce required in the implicit and hybrid flows
1484                        if (nonce == null && (ar.getResponseType().impliesImplicitFlow() || ar.getResponseType().impliesHybridFlow())) {
1485                                String msg = "Missing \"nonce\" parameter: Required in the implicit and hybrid flows";
1486                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1487                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1488                        }
1489                }
1490                
1491                // Check if present (not in JAR)
1492                if (ar.getResponseType() != null) {
1493                        try {
1494                                OIDCResponseTypeValidator.validate(ar.getResponseType());
1495                        } catch (IllegalArgumentException e) {
1496                                String msg = "Unsupported \"response_type\" parameter: " + e.getMessage();
1497                                throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg),
1498                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1499                        }
1500                }
1501                
1502                // Check if present (not in JAR)
1503                if (ar.getScope() != null && ! ar.getScope().contains(OIDCScopeValue.OPENID)) {
1504                        String msg = "The scope must include an \"openid\" value";
1505                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1506                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1507                }
1508                
1509                Display display = null;
1510
1511                if (params.containsKey("display")) {
1512                        try {
1513                                display = Display.parse(MultivaluedMapUtils.getFirstValue(params, "display"));
1514
1515                        } catch (ParseException e) {
1516                                String msg = "Invalid \"display\" parameter: " + e.getMessage();
1517                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1518                                        ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1519                        }
1520                }
1521
1522
1523                String v = MultivaluedMapUtils.getFirstValue(params, "max_age");
1524
1525                int maxAge = -1;
1526
1527                if (StringUtils.isNotBlank(v)) {
1528
1529                        try {
1530                                maxAge = Integer.parseInt(v);
1531
1532                        } catch (NumberFormatException e) {
1533                                String msg = "Invalid \"max_age\" parameter: " + v;
1534                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1535                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1536                        }
1537                }
1538
1539
1540                v = MultivaluedMapUtils.getFirstValue(params, "ui_locales");
1541
1542                List<LangTag> uiLocales = null;
1543
1544                if (StringUtils.isNotBlank(v)) {
1545
1546                        uiLocales = new LinkedList<>();
1547
1548                        StringTokenizer st = new StringTokenizer(v, " ");
1549
1550                        while (st.hasMoreTokens()) {
1551
1552                                try {
1553                                        uiLocales.add(LangTag.parse(st.nextToken()));
1554
1555                                } catch (LangTagException e) {
1556                                        String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage();
1557                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1558                                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1559                                }
1560                        }
1561                }
1562
1563
1564                v = MultivaluedMapUtils.getFirstValue(params, "claims_locales");
1565
1566                List<LangTag> claimsLocales = null;
1567
1568                if (StringUtils.isNotBlank(v)) {
1569
1570                        claimsLocales = new LinkedList<>();
1571
1572                        StringTokenizer st = new StringTokenizer(v, " ");
1573
1574                        while (st.hasMoreTokens()) {
1575
1576                                try {
1577                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1578
1579                                } catch (LangTagException e) {
1580                                        String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage();
1581                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1582                                                                 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1583                                }
1584                        }
1585                }
1586
1587
1588                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1589                
1590                JWT idTokenHint = null;
1591                
1592                if (StringUtils.isNotBlank(v)) {
1593                
1594                        try {
1595                                idTokenHint = JWTParser.parse(v);
1596                                
1597                        } catch (java.text.ParseException e) {
1598                                String msg = "Invalid \"id_token_hint\" 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
1604                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1605
1606
1607                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1608
1609                List<ACR> acrValues = null;
1610
1611                if (StringUtils.isNotBlank(v)) {
1612
1613                        acrValues = new LinkedList<>();
1614
1615                        StringTokenizer st = new StringTokenizer(v, " ");
1616
1617                        while (st.hasMoreTokens()) {
1618
1619                                acrValues.add(new ACR(st.nextToken()));
1620                        }
1621                }
1622
1623
1624                v = MultivaluedMapUtils.getFirstValue(params, "claims");
1625
1626                ClaimsRequest claims = null;
1627
1628                if (StringUtils.isNotBlank(v)) {
1629
1630                        JSONObject jsonObject;
1631
1632                        try {
1633                                jsonObject = JSONObjectUtils.parse(v);
1634
1635                        } catch (ParseException e) {
1636                                String msg = "Invalid \"claims\" parameter: " + e.getMessage();
1637                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1638                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1639                        }
1640                        
1641                        try {
1642                                claims = ClaimsRequest.parse(jsonObject);
1643                        } catch (ParseException e) {
1644                                throw new ParseException(e.getMessage(), e.getErrorObject(),
1645                                                         ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e);
1646                        }
1647                }
1648                
1649                String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose");
1650                
1651                if (purpose != null && (purpose.length() < PURPOSE_MIN_LENGTH || purpose.length() > PURPOSE_MAX_LENGTH)) {
1652                        String msg = "Invalid \"purpose\" parameter: Must not be shorter than " + PURPOSE_MIN_LENGTH + " and longer than " + PURPOSE_MAX_LENGTH + " characters";
1653                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1654                                ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState());
1655                }
1656
1657                // Parse additional custom parameters
1658                Map<String,List<String>> customParams = null;
1659
1660                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1661
1662                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1663                                // We have a custom parameter
1664                                if (customParams == null) {
1665                                        customParams = new HashMap<>();
1666                                }
1667                                customParams.put(p.getKey(), p.getValue());
1668                        }
1669                }
1670
1671
1672                return new AuthenticationRequest(
1673                        uri, ar.getResponseType(), ar.getResponseMode(), ar.getScope(), ar.getClientID(), ar.getRedirectionURI(), ar.getState(), nonce,
1674                        display, ar.getPrompt(), maxAge, uiLocales, claimsLocales,
1675                        idTokenHint, loginHint, acrValues, claims, purpose,
1676                        ar.getRequestObject(), ar.getRequestURI(),
1677                        ar.getCodeChallenge(), ar.getCodeChallengeMethod(),
1678                        ar.getResources(),
1679                        ar.includeGrantedScopes(),
1680                        customParams);
1681        }
1682        
1683        
1684        /**
1685         * Parses an OpenID Connect authentication request from the specified
1686         * URI query string.
1687         *
1688         * <p>Example URI query string:
1689         *
1690         * <pre>
1691         * response_type=token%20id_token
1692         * &amp;client_id=s6BhdRkqt3
1693         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1694         * &amp;scope=openid%20profile
1695         * &amp;state=af0ifjsldkj
1696         * &amp;nonce=n-0S6_WzA2Mj
1697         * </pre>
1698         *
1699         * @param query The URI query string. Must not be {@code null}.
1700         *
1701         * @return The OpenID Connect authentication request.
1702         *
1703         * @throws ParseException If the query string couldn't be parsed to an 
1704         *                        OpenID Connect authentication request.
1705         */
1706        public static AuthenticationRequest parse(final String query)
1707                throws ParseException {
1708        
1709                return parse(null, URLUtils.parseParameters(query));
1710        }
1711
1712
1713        /**
1714         * Parses an OpenID Connect authentication request from the specified
1715         * URI query string.
1716         *
1717         * <p>Example URI query string:
1718         *
1719         * <pre>
1720         * response_type=token%20id_token
1721         * &amp;client_id=s6BhdRkqt3
1722         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1723         * &amp;scope=openid%20profile
1724         * &amp;state=af0ifjsldkj
1725         * &amp;nonce=n-0S6_WzA2Mj
1726         * </pre>
1727         *
1728         * @param uri   The URI of the OAuth 2.0 authorisation endpoint. May be
1729         *              {@code null} if the {@link #toHTTPRequest} method will
1730         *              not be used.
1731         * @param query The URI query string. Must not be {@code null}.
1732         *
1733         * @return The OpenID Connect authentication request.
1734         *
1735         * @throws ParseException If the query string couldn't be parsed to an
1736         *                        OpenID Connect authentication request.
1737         */
1738        public static AuthenticationRequest parse(final URI uri, final String query)
1739                throws ParseException {
1740
1741                return parse(uri, URLUtils.parseParameters(query));
1742        }
1743
1744
1745        /**
1746         * Parses an OpenID Connect authentication request from the specified
1747         * URI.
1748         *
1749         * <p>Example URI:
1750         *
1751         * <pre>
1752         * https://server.example.com/authorize?
1753         * response_type=token%20id_token
1754         * &amp;client_id=s6BhdRkqt3
1755         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1756         * &amp;scope=openid%20profile
1757         * &amp;state=af0ifjsldkj
1758         * &amp;nonce=n-0S6_WzA2Mj
1759         * </pre>
1760         *
1761         * @param uri The URI. Must not be {@code null}.
1762         *
1763         * @return The OpenID Connect authentication request.
1764         *
1765         * @throws ParseException If the query string couldn't be parsed to an
1766         *                        OpenID Connect authentication request.
1767         */
1768        public static AuthenticationRequest parse(final URI uri)
1769                throws ParseException {
1770
1771                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1772        }
1773        
1774        
1775        /**
1776         * Parses an authentication request from the specified HTTP GET or HTTP
1777         * POST request.
1778         *
1779         * <p>Example HTTP request (GET):
1780         *
1781         * <pre>
1782         * https://server.example.com/op/authorize?
1783         * response_type=code%20id_token
1784         * &amp;client_id=s6BhdRkqt3
1785         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1786         * &amp;scope=openid
1787         * &amp;nonce=n-0S6_WzA2Mj
1788         * &amp;state=af0ifjsldkj
1789         * </pre>
1790         *
1791         * @param httpRequest The HTTP request. Must not be {@code null}.
1792         *
1793         * @return The OpenID Connect authentication request.
1794         *
1795         * @throws ParseException If the HTTP request couldn't be parsed to an 
1796         *                        OpenID Connect authentication request.
1797         */
1798        public static AuthenticationRequest parse(final HTTPRequest httpRequest)
1799                throws ParseException {
1800                
1801                String query = httpRequest.getQuery();
1802                
1803                if (query == null)
1804                        throw new ParseException("Missing URI query string");
1805
1806                URI endpointURI;
1807
1808                try {
1809                        endpointURI = httpRequest.getURL().toURI();
1810
1811                } catch (URISyntaxException e) {
1812
1813                        throw new ParseException(e.getMessage(), e);
1814                }
1815                
1816                return parse(endpointURI, query);
1817        }
1818}