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