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