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.oauth2.sdk;
019
020
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.util.*;
026
027import net.jcip.annotations.Immutable;
028
029import com.nimbusds.jwt.JWTClaimsSet;
030import com.nimbusds.oauth2.sdk.http.HTTPRequest;
031import com.nimbusds.oauth2.sdk.id.ClientID;
032import com.nimbusds.oauth2.sdk.id.State;
033import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
034import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
035import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
036import com.nimbusds.oauth2.sdk.util.*;
037
038
039/**
040 * Authorisation request. Used to authenticate an end-user and request the
041 * end-user's consent to grant the client access to a protected resource.
042 * Supports custom request parameters.
043 *
044 * <p>Extending classes may define additional request parameters as well as 
045 * enforce tighter requirements on the base parameters.
046 *
047 * <p>Example HTTP request:
048 *
049 * <pre>
050 * https://server.example.com/authorize?
051 * response_type=code
052 * &amp;client_id=s6BhdRkqt3
053 * &amp;state=xyz
054 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
055 * </pre>
056 *
057 * <p>Related specifications:
058 *
059 * <ul>
060 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
061 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
062 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
063 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
064 *     <li>Resource Indicators for OAuth 2.0
065 *         (draft-ietf-oauth-resource-indicators-00)
066 *     <li>OAuth 2.0 Incremental Authorization
067 *         (draft-ietf-oauth-incremental-authz-00)
068 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
069 *         OAuth 2.0 (JARM)
070 * </ul>
071 */
072@Immutable
073public class AuthorizationRequest extends AbstractRequest {
074
075
076        /**
077         * The registered parameter names.
078         */
079        private static final Set<String> REGISTERED_PARAMETER_NAMES;
080
081
082        static {
083                Set<String> p = new HashSet<>();
084
085                p.add("response_type");
086                p.add("client_id");
087                p.add("redirect_uri");
088                p.add("scope");
089                p.add("state");
090                p.add("response_mode");
091                p.add("code_challenge");
092                p.add("code_challenge_method");
093                p.add("resource");
094                p.add("include_granted_scopes");
095
096                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
097        }
098
099
100        /**
101         * The response type (required).
102         */
103        private final ResponseType rt;
104
105
106        /**
107         * The client identifier (required).
108         */
109        private final ClientID clientID;
110
111
112        /**
113         * The redirection URI where the response will be sent (optional). 
114         */
115        private final URI redirectURI;
116        
117        
118        /**
119         * The scope (optional).
120         */
121        private final Scope scope;
122        
123        
124        /**
125         * The opaque value to maintain state between the request and the 
126         * callback (recommended).
127         */
128        private final State state;
129
130
131        /**
132         * The response mode (optional).
133         */
134        private final ResponseMode rm;
135
136
137        /**
138         * The authorisation code challenge for PKCE (optional).
139         */
140        private final CodeChallenge codeChallenge;
141
142
143        /**
144         * The authorisation code challenge method for PKCE (optional).
145         */
146        private final CodeChallengeMethod codeChallengeMethod;
147        
148        
149        /**
150         * The resource URI(s) (optional).
151         */
152        private final List<URI> resources;
153        
154        
155        /**
156         * Indicates incremental authorisation (optional).
157         */
158        private final boolean includeGrantedScopes;
159
160
161        /**
162         * Custom parameters.
163         */
164        private final Map<String,List<String>> customParams;
165
166
167        /**
168         * Builder for constructing authorisation requests.
169         */
170        public static class Builder {
171
172
173                /**
174                 * The endpoint URI (optional).
175                 */
176                private URI uri;
177
178
179                /**
180                 * The response type (required).
181                 */
182                private final ResponseType rt;
183
184
185                /**
186                 * The client identifier (required).
187                 */
188                private final ClientID clientID;
189
190
191                /**
192                 * The redirection URI where the response will be sent
193                 * (optional).
194                 */
195                private URI redirectURI;
196
197
198                /**
199                 * The scope (optional).
200                 */
201                private Scope scope;
202
203
204                /**
205                 * The opaque value to maintain state between the request and
206                 * the callback (recommended).
207                 */
208                private State state;
209
210
211                /**
212                 * The response mode (optional).
213                 */
214                private ResponseMode rm;
215
216
217                /**
218                 * The authorisation code challenge for PKCE (optional).
219                 */
220                private CodeChallenge codeChallenge;
221
222
223                /**
224                 * The authorisation code challenge method for PKCE (optional).
225                 */
226                private CodeChallengeMethod codeChallengeMethod;
227                
228                
229                /**
230                 * Indicates incremental authorisation (optional).
231                 */
232                private boolean includeGrantedScopes;
233                
234                
235                /**
236                 * The resource URI(s) (optional).
237                 */
238                private List<URI> resources;
239
240
241                /**
242                 * Custom parameters.
243                 */
244                private final Map<String,List<String>> customParams = new HashMap<>();
245
246
247                /**
248                 * Creates a new authorisation request builder.
249                 *
250                 * @param rt       The response type. Corresponds to the
251                 *                 {@code response_type} parameter. Must not be
252                 *                 {@code null}.
253                 * @param clientID The client identifier. Corresponds to the
254                 *                 {@code client_id} parameter. Must not be
255                 *                 {@code null}.
256                 */
257                public Builder(final ResponseType rt, final ClientID clientID) {
258
259                        if (rt == null)
260                                throw new IllegalArgumentException("The response type must not be null");
261
262                        this.rt = rt;
263
264
265                        if (clientID == null)
266                                throw new IllegalArgumentException("The client ID must not be null");
267
268                        this.clientID = clientID;
269                }
270                
271                
272                /**
273                 * Creates a new authorisation request builder from the
274                 * specified request.
275                 *
276                 * @param request The authorisation request. Must not be
277                 *                {@code null}.
278                 */
279                public Builder(final AuthorizationRequest request) {
280                        
281                        uri = request.getEndpointURI();
282                        scope = request.scope;
283                        rt = request.getResponseType();
284                        clientID = request.getClientID();
285                        redirectURI = request.getRedirectionURI();
286                        state = request.getState();
287                        rm = request.getResponseMode();
288                        codeChallenge = request.getCodeChallenge();
289                        codeChallengeMethod = request.getCodeChallengeMethod();
290                        resources = request.getResources();
291                        includeGrantedScopes = request.includeGrantedScopes();
292                        customParams.putAll(request.getCustomParameters());
293                }
294
295
296                /**
297                 * Sets the redirection URI. Corresponds to the optional
298                 * {@code redirection_uri} parameter.
299                 *
300                 * @param redirectURI The redirection URI, {@code null} if not
301                 *                    specified.
302                 *
303                 * @return This builder.
304                 */
305                public Builder redirectionURI(final URI redirectURI) {
306
307                        this.redirectURI = redirectURI;
308                        return this;
309                }
310
311
312                /**
313                 * Sets the scope. Corresponds to the optional {@code scope}
314                 * parameter.
315                 *
316                 * @param scope The scope, {@code null} if not specified.
317                 *
318                 * @return This builder.
319                 */
320                public Builder scope(final Scope scope) {
321
322                        this.scope = scope;
323                        return this;
324                }
325
326
327                /**
328                 * Sets the state. Corresponds to the recommended {@code state}
329                 * parameter.
330                 *
331                 * @param state The state, {@code null} if not specified.
332                 *
333                 * @return This builder.
334                 */
335                public Builder state(final State state) {
336
337                        this.state = state;
338                        return this;
339                }
340
341
342                /**
343                 * Sets the response mode. Corresponds to the optional
344                 * {@code response_mode} parameter. Use of this parameter is
345                 * not recommended unless a non-default response mode is
346                 * requested (e.g. form_post).
347                 *
348                 * @param rm The response mode, {@code null} if not specified.
349                 *
350                 * @return This builder.
351                 */
352                public Builder responseMode(final ResponseMode rm) {
353
354                        this.rm = rm;
355                        return this;
356                }
357
358
359                /**
360                 * Sets the code challenge for Proof Key for Code Exchange
361                 * (PKCE) by public OAuth clients.
362                 *
363                 * @param codeChallenge       The code challenge, {@code null}
364                 *                            if not specified.
365                 * @param codeChallengeMethod The code challenge method,
366                 *                            {@code null} if not specified.
367                 *
368                 * @return This builder.
369                 */
370                @Deprecated
371                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
372
373                        this.codeChallenge = codeChallenge;
374                        this.codeChallengeMethod = codeChallengeMethod;
375                        return this;
376                }
377
378
379                /**
380                 * Sets the code challenge for Proof Key for Code Exchange
381                 * (PKCE) by public OAuth clients.
382                 *
383                 * @param codeVerifier        The code verifier to use to
384                 *                            compute the code challenge,
385                 *                            {@code null} if PKCE is not
386                 *                            specified.
387                 * @param codeChallengeMethod The code challenge method,
388                 *                            {@code null} if not specified.
389                 *                            Defaults to
390                 *                            {@link CodeChallengeMethod#PLAIN}
391                 *                            if a code verifier is specified.
392                 *
393                 * @return This builder.
394                 */
395                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
396
397                        if (codeVerifier != null) {
398                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
399                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
400                                this.codeChallengeMethod = method;
401                        } else {
402                                this.codeChallenge = null;
403                                this.codeChallengeMethod = null;
404                        }
405                        return this;
406                }
407                
408                
409                /**
410                 * Sets the resource server URI(s).
411                 *
412                 * @param resources The resource URI(s), {@code null} if not
413                 *                  specified.
414                 *
415                 * @return This builder.
416                 */
417                public Builder resources(final URI ... resources) {
418                        if (resources != null) {
419                                this.resources = Arrays.asList(resources);
420                        } else {
421                                this.resources = null;
422                        }
423                        return this;
424                }
425                
426                
427                /**
428                 * Requests incremental authorisation.
429                 *
430                 * @param includeGrantedScopes {@code true} to request
431                 *                             incremental authorisation.
432                 *
433                 * @return This builder.
434                 */
435                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
436                        
437                        this.includeGrantedScopes = includeGrantedScopes;
438                        return this;
439                }
440                
441                
442                /**
443                 * Sets a custom parameter.
444                 *
445                 * @param name   The parameter name. Must not be {@code null}.
446                 * @param values The parameter values, {@code null} if not
447                 *               specified.
448                 *
449                 * @return This builder.
450                 */
451                public Builder customParameter(final String name, final String ... values) {
452
453                        if (values == null || values.length == 0) {
454                                customParams.remove(name);
455                        } else {
456                                customParams.put(name, Arrays.asList(values));
457                        }
458                        
459                        return this;
460                }
461
462
463                /**
464                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
465                 * request is intended.
466                 *
467                 * @param uri The endpoint URI, {@code null} if not specified.
468                 *
469                 * @return This builder.
470                 */
471                public Builder endpointURI(final URI uri) {
472
473                        this.uri = uri;
474                        return this;
475                }
476
477
478                /**
479                 * Builds a new authorisation request.
480                 *
481                 * @return The authorisation request.
482                 */
483                public AuthorizationRequest build() {
484
485                        try {
486                                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, customParams);
487                        } catch (IllegalArgumentException e) {
488                                throw new IllegalStateException(e.getMessage(), e);
489                        }
490                }
491        }
492
493
494        /**
495         * Creates a new minimal authorisation request.
496         *
497         * @param uri      The URI of the authorisation endpoint. May be
498         *                 {@code null} if the {@link #toHTTPRequest} method
499         *                 will not be used.
500         * @param rt       The response type. Corresponds to the
501         *                 {@code response_type} parameter. Must not be
502         *                 {@code null}.
503         * @param clientID The client identifier. Corresponds to the
504         *                 {@code client_id} parameter. Must not be
505         *                 {@code null}.
506         */
507        public AuthorizationRequest(final URI uri,
508                                    final ResponseType rt,
509                                    final ClientID clientID) {
510
511                this(uri, rt, null, clientID, null, null, null, null, null, null, false, null);
512        }
513
514
515        /**
516         * Creates a new authorisation request.
517         *
518         * @param uri                 The URI of the authorisation endpoint.
519         *                            May be {@code null} if the
520         *                            {@link #toHTTPRequest} method will not be
521         *                            used.
522         * @param rt                  The response type. Corresponds to the
523         *                            {@code response_type} parameter. Must not
524         *                            be {@code null}.
525         * @param rm                  The response mode. Corresponds to the
526         *                            optional {@code response_mode} parameter.
527         *                            Use of this parameter is not recommended
528         *                            unless a non-default response mode is
529         *                            requested (e.g. form_post).
530         * @param clientID            The client identifier. Corresponds to the
531         *                            {@code client_id} parameter. Must not be
532         *                            {@code null}.
533         * @param redirectURI         The redirection URI. Corresponds to the
534         *                            optional {@code redirect_uri} parameter.
535         *                            {@code null} if not specified.
536         * @param scope               The request scope. Corresponds to the
537         *                            optional {@code scope} parameter.
538         *                            {@code null} if not specified.
539         * @param state               The state. Corresponds to the recommended
540         *                            {@code state} parameter. {@code null} if
541         *                            not specified.
542         */
543        public AuthorizationRequest(final URI uri,
544                                    final ResponseType rt,
545                                    final ResponseMode rm,
546                                    final ClientID clientID,
547                                    final URI redirectURI,
548                                    final Scope scope,
549                                    final State state) {
550
551                this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null);
552        }
553
554
555        /**
556         * Creates a new authorisation request with extension and custom
557         * parameters.
558         *
559         * @param uri                  The URI of the authorisation endpoint.
560         *                             May be {@code null} if the
561         *                             {@link #toHTTPRequest} method will not
562         *                             be used.
563         * @param rt                   The response type. Corresponds to the
564         *                             {@code response_type} parameter. Must
565         *                             not be {@code null}.
566         * @param rm                   The response mode. Corresponds to the
567         *                             optional {@code response_mode}
568         *                             parameter. Use of this parameter is not
569         *                             recommended unless a non-default
570         *                             response mode is requested (e.g.
571         *                             form_post).
572         * @param clientID             The client identifier. Corresponds to
573         *                             the {@code client_id} parameter. Must
574         *                             not be {@code null}.
575         * @param redirectURI          The redirection URI. Corresponds to the
576         *                             optional {@code redirect_uri} parameter.
577         *                             {@code null} if not specified.
578         * @param scope                The request scope. Corresponds to the
579         *                             optional {@code scope} parameter.
580         *                             {@code null} if not specified.
581         * @param state                The state. Corresponds to the
582         *                             recommended {@code state} parameter.
583         *                             {@code null} if not specified.
584         * @param codeChallenge        The code challenge for PKCE,
585         *                             {@code null} if not specified.
586         * @param codeChallengeMethod  The code challenge method for PKCE,
587         *                             {@code null} if not specified.
588         * @param resources            The resource URI(s), {@code null} if not
589         *                             specified.
590         * @param includeGrantedScopes {@code true} to request incremental
591         *                             authorisation.
592         * @param customParams         Custom parameters, empty map or
593         *                             {@code null} if none.
594         */
595        public AuthorizationRequest(final URI uri,
596                                    final ResponseType rt,
597                                    final ResponseMode rm,
598                                    final ClientID clientID,
599                                    final URI redirectURI,
600                                    final Scope scope,
601                                    final State state,
602                                    final CodeChallenge codeChallenge,
603                                    final CodeChallengeMethod codeChallengeMethod,
604                                    final List<URI> resources,
605                                    final boolean includeGrantedScopes,
606                                    final Map<String,List<String>> customParams) {
607
608                super(uri);
609
610                if (rt == null)
611                        throw new IllegalArgumentException("The response type must not be null");
612
613                this.rt = rt;
614
615                this.rm = rm;
616
617
618                if (clientID == null)
619                        throw new IllegalArgumentException("The client ID must not be null");
620
621                this.clientID = clientID;
622
623
624                this.redirectURI = redirectURI;
625                this.scope = scope;
626                this.state = state;
627
628                this.codeChallenge = codeChallenge;
629                this.codeChallengeMethod = codeChallengeMethod;
630                
631                if (resources != null) {
632                        for (URI resourceURI: resources) {
633                                if (! ResourceUtils.isValidResourceURI(resourceURI))
634                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
635                        }
636                }
637                
638                this.resources = resources;
639                
640                this.includeGrantedScopes = includeGrantedScopes;
641
642                if (MapUtils.isNotEmpty(customParams)) {
643                        this.customParams = Collections.unmodifiableMap(customParams);
644                } else {
645                        this.customParams = Collections.emptyMap();
646                }
647        }
648
649
650        /**
651         * Returns the registered (standard) OAuth 2.0 authorisation request
652         * parameter names.
653         *
654         * @return The registered OAuth 2.0 authorisation request parameter
655         *         names, as a unmodifiable set.
656         */
657        public static Set<String> getRegisteredParameterNames() {
658
659                return REGISTERED_PARAMETER_NAMES;
660        }
661
662
663        /**
664         * Gets the response type. Corresponds to the {@code response_type}
665         * parameter.
666         *
667         * @return The response type.
668         */
669        public ResponseType getResponseType() {
670        
671                return rt;
672        }
673
674
675        /**
676         * Gets the optional response mode. Corresponds to the optional
677         * {@code response_mode} parameter.
678         *
679         * @return The response mode, {@code null} if not specified.
680         */
681        public ResponseMode getResponseMode() {
682
683                return rm;
684        }
685
686
687        /**
688         * Returns the implied response mode, determined by the optional
689         * {@code response_mode} parameter, and if that isn't specified, by
690         * the {@code response_type}.
691         *
692         * @return The implied response mode.
693         */
694        public ResponseMode impliedResponseMode() {
695
696                if (rm != null) {
697                        
698                        if (ResponseMode.JWT.equals(rm)) {
699                                // https://openid.net//specs/openid-financial-api-jarm.html#response-mode-jwt
700                                if (rt.impliesImplicitFlow() || rt.impliesHybridFlow()) {
701                                        return ResponseMode.FRAGMENT_JWT;
702                                } else {
703                                        return ResponseMode.QUERY_JWT;
704                                }
705                        }
706                        
707                        return rm;
708                        
709                } else if (rt.impliesImplicitFlow() || rt.impliesHybridFlow()) {
710                        return ResponseMode.FRAGMENT;
711                } else {
712                        return ResponseMode.QUERY;
713                }
714        }
715
716
717        /**
718         * Gets the client identifier. Corresponds to the {@code client_id} 
719         * parameter.
720         *
721         * @return The client identifier.
722         */
723        public ClientID getClientID() {
724        
725                return clientID;
726        }
727
728
729        /**
730         * Gets the redirection URI. Corresponds to the optional 
731         * {@code redirection_uri} parameter.
732         *
733         * @return The redirection URI, {@code null} if not specified.
734         */
735        public URI getRedirectionURI() {
736        
737                return redirectURI;
738        }
739        
740        
741        /**
742         * Gets the scope. Corresponds to the optional {@code scope} parameter.
743         *
744         * @return The scope, {@code null} if not specified.
745         */
746        public Scope getScope() {
747        
748                return scope;
749        }
750        
751        
752        /**
753         * Gets the state. Corresponds to the recommended {@code state} 
754         * parameter.
755         *
756         * @return The state, {@code null} if not specified.
757         */
758        public State getState() {
759        
760                return state;
761        }
762
763
764        /**
765         * Returns the code challenge for PKCE.
766         *
767         * @return The code challenge, {@code null} if not specified.
768         */
769        public CodeChallenge getCodeChallenge() {
770
771                return codeChallenge;
772        }
773
774
775        /**
776         * Returns the code challenge method for PKCE.
777         *
778         * @return The code challenge method, {@code null} if not specified.
779         */
780        public CodeChallengeMethod getCodeChallengeMethod() {
781
782                return codeChallengeMethod;
783        }
784        
785        
786        /**
787         * Returns the resource server URI.
788         *
789         * @return The resource URI(s), {@code null} if not specified.
790         */
791        public List<URI> getResources() {
792                
793                return resources;
794        }
795        
796        
797        /**
798         * Returns {@code true} if incremental authorisation is requested.
799         *
800         * @return {@code true} if incremental authorisation is requested,
801         *         else {@code false}.
802         */
803        public boolean includeGrantedScopes() {
804                
805                return includeGrantedScopes;
806        }
807        
808        
809        /**
810         * Returns the additional custom parameters.
811         *
812         * @return The additional custom parameters as a unmodifiable map,
813         *         empty map if none.
814         */
815        public Map<String,List<String>> getCustomParameters () {
816
817                return customParams;
818        }
819
820
821        /**
822         * Returns the specified custom parameter.
823         *
824         * @param name The parameter name. Must not be {@code null}.
825         *
826         * @return The parameter value(s), {@code null} if not specified.
827         */
828        public List<String> getCustomParameter(final String name) {
829
830                return customParams.get(name);
831        }
832
833
834        /**
835         * Returns the URI query parameters for this authorisation request.
836         * Query parameters which are part of the authorisation endpoint are
837         * not included.
838         *
839         * <p>Example parameters:
840         *
841         * <pre>
842         * response_type = code
843         * client_id     = s6BhdRkqt3
844         * state         = xyz
845         * redirect_uri  = https://client.example.com/cb
846         * </pre>
847         * 
848         * @return The parameters.
849         */
850        public Map<String,List<String>> toParameters() {
851
852                Map<String,List<String>> params = new LinkedHashMap<>();
853
854                // Put custom params first, so they may be overwritten by std params
855                params.putAll(customParams);
856                
857                params.put("response_type", Collections.singletonList(rt.toString()));
858                params.put("client_id", Collections.singletonList(clientID.getValue()));
859
860                if (rm != null) {
861                        params.put("response_mode", Collections.singletonList(rm.getValue()));
862                }
863
864                if (redirectURI != null)
865                        params.put("redirect_uri", Collections.singletonList(redirectURI.toString()));
866
867                if (scope != null)
868                        params.put("scope", Collections.singletonList(scope.toString()));
869                
870                if (state != null)
871                        params.put("state", Collections.singletonList(state.getValue()));
872
873                if (codeChallenge != null) {
874                        params.put("code_challenge", Collections.singletonList(codeChallenge.getValue()));
875
876                        if (codeChallengeMethod != null) {
877                                params.put("code_challenge_method", Collections.singletonList(codeChallengeMethod.getValue()));
878                        }
879                }
880                
881                if (includeGrantedScopes) {
882                        params.put("include_granted_scopes", Collections.singletonList("true"));
883                }
884                
885                if (resources != null) {
886                        List<String> resourceValues = new LinkedList<>();
887                        for (URI resourceURI: resources) {
888                                if (resourceURI != null) {
889                                        resourceValues.add(resourceURI.toString());
890                                }
891                        }
892                        params.put("resource", resourceValues);
893                }
894
895                return params;
896        }
897        
898        
899        /**
900         * Returns the parameters for this authorisation request as a JSON Web
901         * Token (JWT) claims set. Intended for creating a request object.
902         *
903         * @return The parameters as JWT claim set.
904         */
905        public JWTClaimsSet toJWTClaimsSet() {
906                
907                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
908        }
909        
910        
911        /**
912         * Returns the URI query string for this authorisation request.
913         *
914         * <p>Note that the '?' character preceding the query string in an URI
915         * is not included in the returned string.
916         *
917         * <p>Example URI query string:
918         *
919         * <pre>
920         * response_type=code
921         * &amp;client_id=s6BhdRkqt3
922         * &amp;state=xyz
923         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
924         * </pre>
925         * 
926         * @return The URI query string.
927         */
928        public String toQueryString() {
929                
930                Map<String, List<String>> params = new HashMap<>();
931                if (getEndpointURI() != null) {
932                        params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery()));
933                }
934                params.putAll(toParameters());
935                
936                return URLUtils.serializeParameters(params);
937        }
938
939
940        /**
941         * Returns the complete URI representation for this authorisation
942         * request, consisting of the {@link #getEndpointURI authorization
943         * endpoint URI} with the {@link #toQueryString query string} appended.
944         *
945         * <p>Example URI:
946         *
947         * <pre>
948         * https://server.example.com/authorize?
949         * response_type=code
950         * &amp;client_id=s6BhdRkqt3
951         * &amp;state=xyz
952         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
953         * </pre>
954         *
955         * @return The URI representation.
956         */
957        public URI toURI() {
958
959                if (getEndpointURI() == null)
960                        throw new SerializeException("The authorization endpoint URI is not specified");
961                
962                StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString());
963                sb.append('?');
964                sb.append(toQueryString());
965                try {
966                        return new URI(sb.toString());
967                } catch (URISyntaxException e) {
968                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
969                }
970        }
971        
972        
973        /**
974         * Returns the matching HTTP request.
975         *
976         * @param method The HTTP request method which can be GET or POST. Must
977         *               not be {@code null}.
978         *
979         * @return The HTTP request.
980         */
981        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
982                
983                if (getEndpointURI() == null)
984                        throw new SerializeException("The endpoint URI is not specified");
985                
986                HTTPRequest httpRequest;
987
988                URL endpointURL;
989
990                try {
991                        endpointURL = getEndpointURI().toURL();
992
993                } catch (MalformedURLException e) {
994
995                        throw new SerializeException(e.getMessage(), e);
996                }
997                
998                if (method.equals(HTTPRequest.Method.GET)) {
999
1000                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
1001
1002                } else if (method.equals(HTTPRequest.Method.POST)) {
1003
1004                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
1005
1006                } else {
1007
1008                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
1009                }
1010                
1011                httpRequest.setQuery(toQueryString());
1012                
1013                return httpRequest;
1014        }
1015        
1016        
1017        @Override
1018        public HTTPRequest toHTTPRequest() {
1019        
1020                return toHTTPRequest(HTTPRequest.Method.GET);
1021        }
1022
1023
1024        /**
1025         * Parses an authorisation request from the specified URI query
1026         * parameters.
1027         *
1028         * <p>Example parameters:
1029         *
1030         * <pre>
1031         * response_type = code
1032         * client_id     = s6BhdRkqt3
1033         * state         = xyz
1034         * redirect_uri  = https://client.example.com/cb
1035         * </pre>
1036         *
1037         * @param params The parameters. Must not be {@code null}.
1038         *
1039         * @return The authorisation request.
1040         *
1041         * @throws ParseException If the parameters couldn't be parsed to an
1042         *                        authorisation request.
1043         */
1044        public static AuthorizationRequest parse(final Map<String,List<String>> params)
1045                throws ParseException {
1046
1047                return parse(null, params);
1048        }
1049
1050
1051        /**
1052         * Parses an authorisation request from the specified URI and query
1053         * parameters.
1054         *
1055         * <p>Example parameters:
1056         *
1057         * <pre>
1058         * response_type = code
1059         * client_id     = s6BhdRkqt3
1060         * state         = xyz
1061         * redirect_uri  = https://client.example.com/cb
1062         * </pre>
1063         *
1064         * @param uri    The URI of the authorisation endpoint. May be
1065         *               {@code null} if the {@link #toHTTPRequest()} method
1066         *               will not be used.
1067         * @param params The parameters. Must not be {@code null}.
1068         *
1069         * @return The authorisation request.
1070         *
1071         * @throws ParseException If the parameters couldn't be parsed to an
1072         *                        authorisation request.
1073         */
1074        public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params)
1075                throws ParseException {
1076
1077                // Parse mandatory client ID first
1078                String v = MultivaluedMapUtils.getFirstValue(params, "client_id");
1079
1080                if (StringUtils.isBlank(v)) {
1081                        String msg = "Missing \"client_id\" parameter";
1082                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1083                }
1084
1085                ClientID clientID = new ClientID(v);
1086
1087
1088                // Parse optional redirection URI second
1089                v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri");
1090
1091                URI redirectURI = null;
1092
1093                if (StringUtils.isNotBlank(v)) {
1094
1095                        try {
1096                                redirectURI = new URI(v);
1097
1098                        } catch (URISyntaxException e) {
1099                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
1100                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1101                                                         clientID, null, null, null, e);
1102                        }
1103                }
1104
1105
1106                // Parse optional state third
1107                State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state"));
1108
1109
1110                // Parse mandatory response type
1111                v = MultivaluedMapUtils.getFirstValue(params, "response_type");
1112
1113                ResponseType rt;
1114
1115                try {
1116                        rt = ResponseType.parse(v);
1117
1118                } catch (ParseException e) {
1119                        // Only cause
1120                        String msg = "Missing \"response_type\" parameter";
1121                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1122                                                 clientID, redirectURI, null, state, e);
1123                }
1124
1125
1126                // Parse the optional response mode
1127                v = MultivaluedMapUtils.getFirstValue(params, "response_mode");
1128
1129                ResponseMode rm = null;
1130
1131                if (StringUtils.isNotBlank(v)) {
1132                        rm = new ResponseMode(v);
1133                }
1134
1135
1136                // Parse optional scope
1137                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1138
1139                Scope scope = null;
1140
1141                if (StringUtils.isNotBlank(v))
1142                        scope = Scope.parse(v);
1143
1144
1145                // Parse optional code challenge and method for PKCE
1146                CodeChallenge codeChallenge = null;
1147                CodeChallengeMethod codeChallengeMethod = null;
1148
1149                v = MultivaluedMapUtils.getFirstValue(params, "code_challenge");
1150
1151                if (StringUtils.isNotBlank(v))
1152                        codeChallenge = CodeChallenge.parse(v);
1153
1154                if (codeChallenge != null) {
1155
1156                        v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method");
1157
1158                        if (StringUtils.isNotBlank(v))
1159                                codeChallengeMethod = CodeChallengeMethod.parse(v);
1160                }
1161                
1162                List<URI> resources = null;
1163                
1164                List<String> vList = params.get("resource");
1165                
1166                if (vList != null) {
1167                        
1168                        resources = new LinkedList<>();
1169                        
1170                        for (String uriValue: vList) {
1171                                
1172                                if (uriValue == null)
1173                                        continue;
1174                                
1175                                String errMsg = "Invalid \"resource\" parameter: Must be an absolute URI and with no query or fragment: " + uriValue;
1176                                
1177                                URI resourceURI;
1178                                try {
1179                                        resourceURI = new URI(uriValue);
1180                                } catch (URISyntaxException e) {
1181                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1182                                                clientID, redirectURI, null, state, e);
1183                                }
1184                                
1185                                if (! ResourceUtils.isValidResourceURI(resourceURI)) {
1186                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1187                                                clientID, redirectURI, null, state, null);
1188                                }
1189                                
1190                                resources.add(resourceURI);
1191                        }
1192                }
1193                
1194                boolean includeGrantedScopes = false;
1195                v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes");
1196                if ("true".equals(v)) {
1197                        includeGrantedScopes = true;
1198                }
1199                
1200                // Parse custom parameters
1201                Map<String,List<String>> customParams = null;
1202
1203                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1204
1205                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1206                                // We have a custom parameter
1207                                if (customParams == null) {
1208                                        customParams = new HashMap<>();
1209                                }
1210                                customParams.put(p.getKey(), p.getValue());
1211                        }
1212                }
1213
1214
1215                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, customParams);
1216        }
1217
1218
1219        /**
1220         * Parses an authorisation request from the specified URI query string.
1221         *
1222         * <p>Example URI query string:
1223         *
1224         * <pre>
1225         * response_type=code
1226         * &amp;client_id=s6BhdRkqt3
1227         * &amp;state=xyz
1228         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1229         * </pre>
1230         *
1231         * @param query The URI query string. Must not be {@code null}.
1232         *
1233         * @return The authorisation request.
1234         *
1235         * @throws ParseException If the query string couldn't be parsed to an
1236         *                        authorisation request.
1237         */
1238        public static AuthorizationRequest parse(final String query)
1239                throws ParseException {
1240
1241                return parse(null, URLUtils.parseParameters(query));
1242        }
1243        
1244        
1245        /**
1246         * Parses an authorisation request from the specified URI and query
1247         * string.
1248         *
1249         * <p>Example URI query string:
1250         *
1251         * <pre>
1252         * response_type=code
1253         * &amp;client_id=s6BhdRkqt3
1254         * &amp;state=xyz
1255         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1256         * </pre>
1257         *
1258         * @param uri   The URI of the authorisation endpoint. May be 
1259         *              {@code null} if the {@link #toHTTPRequest()} method
1260         *              will not be used.
1261         * @param query The URI query string. Must not be {@code null}.
1262         *
1263         * @return The authorisation request.
1264         *
1265         * @throws ParseException If the query string couldn't be parsed to an 
1266         *                        authorisation request.
1267         */
1268        public static AuthorizationRequest parse(final URI uri, final String query)
1269                throws ParseException {
1270        
1271                return parse(uri, URLUtils.parseParameters(query));
1272        }
1273
1274
1275        /**
1276         * Parses an authorisation request from the specified URI.
1277         *
1278         * <p>Example URI:
1279         *
1280         * <pre>
1281         * https://server.example.com/authorize?
1282         * response_type=code
1283         * &amp;client_id=s6BhdRkqt3
1284         * &amp;state=xyz
1285         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1286         * </pre>
1287         *
1288         * @param uri The URI. Must not be {@code null}.
1289         *
1290         * @return The authorisation request.
1291         *
1292         * @throws ParseException If the URI couldn't be parsed to an
1293         *                        authorisation request.
1294         */
1295        public static AuthorizationRequest parse(final URI uri)
1296                throws ParseException {
1297
1298                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1299        }
1300        
1301        
1302        /**
1303         * Parses an authorisation request from the specified HTTP request.
1304         *
1305         * <p>Example HTTP request (GET):
1306         *
1307         * <pre>
1308         * https://server.example.com/authorize?
1309         * response_type=code
1310         * &amp;client_id=s6BhdRkqt3
1311         * &amp;state=xyz
1312         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1313         * </pre>
1314         *
1315         * @param httpRequest The HTTP request. Must not be {@code null}.
1316         *
1317         * @return The authorisation request.
1318         *
1319         * @throws ParseException If the HTTP request couldn't be parsed to an 
1320         *                        authorisation request.
1321         */
1322        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
1323                throws ParseException {
1324                
1325                String query = httpRequest.getQuery();
1326                
1327                if (query == null)
1328                        throw new ParseException("Missing URI query string");
1329
1330                try {
1331                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
1332
1333                } catch (URISyntaxException e) {
1334
1335                        throw new ParseException(e.getMessage(), e);
1336                }
1337        }
1338}