001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.util.*;
026
027import net.jcip.annotations.Immutable;
028
029import com.nimbusds.jwt.JWT;
030import com.nimbusds.jwt.JWTClaimsSet;
031import com.nimbusds.jwt.JWTParser;
032import com.nimbusds.jwt.SignedJWT;
033import com.nimbusds.oauth2.sdk.http.HTTPRequest;
034import com.nimbusds.oauth2.sdk.id.ClientID;
035import com.nimbusds.oauth2.sdk.id.State;
036import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
037import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
038import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
039import com.nimbusds.oauth2.sdk.util.*;
040import com.nimbusds.openid.connect.sdk.Prompt;
041
042
043/**
044 * Authorisation request. Used to authenticate an end-user and request the
045 * end-user's consent to grant the client access to a protected resource.
046 * Supports custom request parameters.
047 *
048 * <p>Extending classes may define additional request parameters as well as 
049 * enforce tighter requirements on the base parameters.
050 *
051 * <p>Example HTTP request:
052 *
053 * <pre>
054 * https://server.example.com/authorize?
055 * response_type=code
056 * &amp;client_id=s6BhdRkqt3
057 * &amp;state=xyz
058 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
059 * </pre>
060 *
061 * <p>Related specifications:
062 *
063 * <ul>
064 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
065 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
066 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
067 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
068 *     <li>Resource Indicators for OAuth 2.0 (RFC 8707)
069 *     <li>OAuth 2.0 Incremental Authorization
070 *         (draft-ietf-oauth-incremental-authz-04)
071 *     <li>The OAuth 2.0 Authorization Framework: JWT Secured Authorization
072 *         Request (JAR) draft-ietf-oauth-jwsreq-29
073 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
074 *         OAuth 2.0 (JARM)
075 * </ul>
076 */
077@Immutable
078public class AuthorizationRequest extends AbstractRequest {
079
080
081        /**
082         * The registered parameter names.
083         */
084        private static final Set<String> REGISTERED_PARAMETER_NAMES;
085
086
087        static {
088                Set<String> p = new HashSet<>();
089
090                p.add("response_type");
091                p.add("client_id");
092                p.add("redirect_uri");
093                p.add("scope");
094                p.add("state");
095                p.add("response_mode");
096                p.add("code_challenge");
097                p.add("code_challenge_method");
098                p.add("resource");
099                p.add("include_granted_scopes");
100                p.add("request_uri");
101                p.add("request");
102                p.add("prompt");
103
104                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
105        }
106        
107        
108        /**
109         * The response type (required unless in JAR).
110         */
111        private final ResponseType rt;
112
113
114        /**
115         * The client identifier (required).
116         */
117        private final ClientID clientID;
118
119
120        /**
121         * The redirection URI where the response will be sent (optional). 
122         */
123        private final URI redirectURI;
124        
125        
126        /**
127         * The scope (optional).
128         */
129        private final Scope scope;
130        
131        
132        /**
133         * The opaque value to maintain state between the request and the 
134         * callback (recommended).
135         */
136        private final State state;
137
138
139        /**
140         * The response mode (optional).
141         */
142        private final ResponseMode rm;
143
144
145        /**
146         * The authorisation code challenge for PKCE (optional).
147         */
148        private final CodeChallenge codeChallenge;
149
150
151        /**
152         * The authorisation code challenge method for PKCE (optional).
153         */
154        private final CodeChallengeMethod codeChallengeMethod;
155        
156        
157        /**
158         * The resource URI(s) (optional).
159         */
160        private final List<URI> resources;
161        
162        
163        /**
164         * Indicates incremental authorisation (optional).
165         */
166        private final boolean includeGrantedScopes;
167        
168        
169        /**
170         * Request object (optional).
171         */
172        private final JWT requestObject;
173        
174        
175        /**
176         * Request object URI (optional).
177         */
178        private final URI requestURI;
179        
180        
181        /**
182         * The requested prompt (optional).
183         */
184        protected final Prompt prompt;
185
186
187        /**
188         * Custom parameters.
189         */
190        private final Map<String,List<String>> customParams;
191
192
193        /**
194         * Builder for constructing authorisation requests.
195         */
196        public static class Builder {
197
198
199                /**
200                 * The endpoint URI (optional).
201                 */
202                private URI uri;
203
204
205                /**
206                 * The response type (required unless in JAR).
207                 */
208                private ResponseType rt;
209
210
211                /**
212                 * The client identifier (required).
213                 */
214                private final ClientID clientID;
215
216
217                /**
218                 * The redirection URI where the response will be sent
219                 * (optional).
220                 */
221                private URI redirectURI;
222
223
224                /**
225                 * The scope (optional).
226                 */
227                private Scope scope;
228
229
230                /**
231                 * The opaque value to maintain state between the request and
232                 * the callback (recommended).
233                 */
234                private State state;
235
236
237                /**
238                 * The response mode (optional).
239                 */
240                private ResponseMode rm;
241
242
243                /**
244                 * The authorisation code challenge for PKCE (optional).
245                 */
246                private CodeChallenge codeChallenge;
247
248
249                /**
250                 * The authorisation code challenge method for PKCE (optional).
251                 */
252                private CodeChallengeMethod codeChallengeMethod;
253                
254                
255                /**
256                 * Indicates incremental authorisation (optional).
257                 */
258                private boolean includeGrantedScopes;
259                
260                
261                /**
262                 * The resource URI(s) (optional).
263                 */
264                private List<URI> resources;
265                
266                
267                /**
268                 * Request object (optional).
269                 */
270                private JWT requestObject;
271                
272                
273                /**
274                 * Request object URI (optional).
275                 */
276                private URI requestURI;
277                
278                
279                /**
280                 * The requested prompt (optional).
281                 */
282                private Prompt prompt;
283
284
285                /**
286                 * Custom parameters.
287                 */
288                private final Map<String,List<String>> customParams = new HashMap<>();
289
290
291                /**
292                 * Creates a new authorisation request builder.
293                 *
294                 * @param rt       The response type. Corresponds to the
295                 *                 {@code response_type} parameter. Must not be
296                 *                 {@code null}.
297                 * @param clientID The client identifier. Corresponds to the
298                 *                 {@code client_id} parameter. Must not be
299                 *                 {@code null}.
300                 */
301                public Builder(final ResponseType rt, final ClientID clientID) {
302
303                        if (rt == null)
304                                throw new IllegalArgumentException("The response type must not be null");
305
306                        this.rt = rt;
307
308
309                        if (clientID == null)
310                                throw new IllegalArgumentException("The client ID must not be null");
311
312                        this.clientID = clientID;
313                }
314                
315                
316                /**
317                 * Creates a new JWT secured authorisation request (JAR)
318                 * builder.
319                 *
320                 * @param requestObject The request object. Must not be
321                 *                      {@code null}.'
322                 * @param clientID      The client ID. Must not be
323                 *                      {@code null}.
324                 */
325                public Builder(final JWT requestObject, final ClientID clientID) {
326                        
327                        if (requestObject == null)
328                                throw new IllegalArgumentException("The request object must not be null");
329                        
330                        this.requestObject = requestObject;
331                        
332                        if (clientID == null)
333                                throw new IllegalArgumentException("The client ID must not be null");
334                        
335                        this.clientID = clientID;
336                }
337                
338                
339                /**
340                 * Creates a new JWT secured authorisation request (JAR)
341                 * builder.
342                 *
343                 * @param requestURI The request object URI. Must not be
344                 *                   {@code null}.
345                 * @param clientID   The client ID. Must not be {@code null}.
346                 */
347                public Builder(final URI requestURI, final ClientID clientID) {
348                        
349                        if (requestURI == null)
350                                throw new IllegalArgumentException("The request URI must not be null");
351                        
352                        this.requestURI = requestURI;
353                        
354                        if (clientID == null)
355                                throw new IllegalArgumentException("The client ID must not be null");
356                        
357                        this.clientID = clientID;
358                }
359                
360                
361                /**
362                 * Creates a new authorisation request builder from the
363                 * specified request.
364                 *
365                 * @param request The authorisation request. Must not be
366                 *                {@code null}.
367                 */
368                public Builder(final AuthorizationRequest request) {
369                        
370                        uri = request.getEndpointURI();
371                        scope = request.scope;
372                        rt = request.getResponseType();
373                        clientID = request.getClientID();
374                        redirectURI = request.getRedirectionURI();
375                        state = request.getState();
376                        rm = request.getResponseMode();
377                        codeChallenge = request.getCodeChallenge();
378                        codeChallengeMethod = request.getCodeChallengeMethod();
379                        resources = request.getResources();
380                        includeGrantedScopes = request.includeGrantedScopes();
381                        requestObject = request.requestObject;
382                        requestURI = request.requestURI;
383                        prompt = request.prompt;
384                        customParams.putAll(request.getCustomParameters());
385                }
386                
387                
388                /**
389                 * Sets the response type. Corresponds to the
390                 * {@code response_type} parameter.
391                 *
392                 * @param rt The response type. Must not be {@code null}.
393                 *
394                 * @return This builder.
395                 */
396                public Builder responseType(final ResponseType rt) {
397                        
398                        if (rt == null)
399                                throw new IllegalArgumentException("The response type must not be null");
400                        
401                        this.rt = rt;
402                        return this;
403                }
404
405
406                /**
407                 * Sets the redirection URI. Corresponds to the optional
408                 * {@code redirection_uri} parameter.
409                 *
410                 * @param redirectURI The redirection URI, {@code null} if not
411                 *                    specified.
412                 *
413                 * @return This builder.
414                 */
415                public Builder redirectionURI(final URI redirectURI) {
416
417                        this.redirectURI = redirectURI;
418                        return this;
419                }
420
421
422                /**
423                 * Sets the scope. Corresponds to the optional {@code scope}
424                 * parameter.
425                 *
426                 * @param scope The scope, {@code null} if not specified.
427                 *
428                 * @return This builder.
429                 */
430                public Builder scope(final Scope scope) {
431
432                        this.scope = scope;
433                        return this;
434                }
435
436
437                /**
438                 * Sets the state. Corresponds to the recommended {@code state}
439                 * parameter.
440                 *
441                 * @param state The state, {@code null} if not specified.
442                 *
443                 * @return This builder.
444                 */
445                public Builder state(final State state) {
446
447                        this.state = state;
448                        return this;
449                }
450
451
452                /**
453                 * Sets the response mode. Corresponds to the optional
454                 * {@code response_mode} parameter. Use of this parameter is
455                 * not recommended unless a non-default response mode is
456                 * requested (e.g. form_post).
457                 *
458                 * @param rm The response mode, {@code null} if not specified.
459                 *
460                 * @return This builder.
461                 */
462                public Builder responseMode(final ResponseMode rm) {
463
464                        this.rm = rm;
465                        return this;
466                }
467
468
469                /**
470                 * Sets the code challenge for Proof Key for Code Exchange
471                 * (PKCE) by public OAuth clients.
472                 *
473                 * @param codeChallenge       The code challenge, {@code null}
474                 *                            if not specified.
475                 * @param codeChallengeMethod The code challenge method,
476                 *                            {@code null} if not specified.
477                 *
478                 * @return This builder.
479                 */
480                @Deprecated
481                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
482
483                        this.codeChallenge = codeChallenge;
484                        this.codeChallengeMethod = codeChallengeMethod;
485                        return this;
486                }
487
488
489                /**
490                 * Sets the code challenge for Proof Key for Code Exchange
491                 * (PKCE) by public OAuth clients.
492                 *
493                 * @param codeVerifier        The code verifier to use to
494                 *                            compute the code challenge,
495                 *                            {@code null} if PKCE is not
496                 *                            specified.
497                 * @param codeChallengeMethod The code challenge method,
498                 *                            {@code null} if not specified.
499                 *                            Defaults to
500                 *                            {@link CodeChallengeMethod#PLAIN}
501                 *                            if a code verifier is specified.
502                 *
503                 * @return This builder.
504                 */
505                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
506
507                        if (codeVerifier != null) {
508                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
509                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
510                                this.codeChallengeMethod = method;
511                        } else {
512                                this.codeChallenge = null;
513                                this.codeChallengeMethod = null;
514                        }
515                        return this;
516                }
517                
518                
519                /**
520                 * Sets the resource server URI(s).
521                 *
522                 * @param resources The resource URI(s), {@code null} if not
523                 *                  specified.
524                 *
525                 * @return This builder.
526                 */
527                public Builder resources(final URI ... resources) {
528                        if (resources != null) {
529                                this.resources = Arrays.asList(resources);
530                        } else {
531                                this.resources = null;
532                        }
533                        return this;
534                }
535                
536                
537                /**
538                 * Requests incremental authorisation.
539                 *
540                 * @param includeGrantedScopes {@code true} to request
541                 *                             incremental authorisation.
542                 *
543                 * @return This builder.
544                 */
545                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
546                        
547                        this.includeGrantedScopes = includeGrantedScopes;
548                        return this;
549                }
550                
551                
552                /**
553                 * Sets the request object. Corresponds to the optional
554                 * {@code request} parameter. Must not be specified together
555                 * with a request object URI.
556                 *
557                 * @param requestObject The request object, {@code null} if not
558                 *                      specified.
559                 *
560                 * @return This builder.
561                 */
562                public Builder requestObject(final JWT requestObject) {
563                        
564                        this.requestObject = requestObject;
565                        return this;
566                }
567                
568                
569                /**
570                 * Sets the request object URI. Corresponds to the optional
571                 * {@code request_uri} parameter. Must not be specified
572                 * together with a request object.
573                 *
574                 * @param requestURI The request object URI, {@code null} if
575                 *                   not specified.
576                 *
577                 * @return This builder.
578                 */
579                public Builder requestURI(final URI requestURI) {
580                        
581                        this.requestURI = requestURI;
582                        return this;
583                }
584                
585                
586                /**
587                 * Sets the requested prompt. Corresponds to the optional
588                 * {@code prompt} parameter.
589                 *
590                 * @param prompt The requested prompt, {@code null} if not
591                 *               specified.
592                 *
593                 * @return This builder.
594                 */
595                public Builder prompt(final Prompt prompt) {
596                        
597                        this.prompt = prompt;
598                        return this;
599                }
600                
601                
602                /**
603                 * Sets a custom parameter.
604                 *
605                 * @param name   The parameter name. Must not be {@code null}.
606                 * @param values The parameter values, {@code null} if not
607                 *               specified.
608                 *
609                 * @return This builder.
610                 */
611                public Builder customParameter(final String name, final String ... values) {
612
613                        if (values == null || values.length == 0) {
614                                customParams.remove(name);
615                        } else {
616                                customParams.put(name, Arrays.asList(values));
617                        }
618                        
619                        return this;
620                }
621
622
623                /**
624                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
625                 * request is intended.
626                 *
627                 * @param uri The endpoint URI, {@code null} if not specified.
628                 *
629                 * @return This builder.
630                 */
631                public Builder endpointURI(final URI uri) {
632
633                        this.uri = uri;
634                        return this;
635                }
636
637
638                /**
639                 * Builds a new authorisation request.
640                 *
641                 * @return The authorisation request.
642                 */
643                public AuthorizationRequest build() {
644
645                        try {
646                                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
647                                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
648                                        requestObject, requestURI,
649                                        prompt,
650                                        customParams);
651                        } catch (IllegalArgumentException e) {
652                                throw new IllegalStateException(e.getMessage(), e);
653                        }
654                }
655        }
656
657
658        /**
659         * Creates a new minimal authorisation request.
660         *
661         * @param uri      The URI of the authorisation endpoint. May be
662         *                 {@code null} if the {@link #toHTTPRequest} method
663         *                 will not be used.
664         * @param rt       The response type. Corresponds to the
665         *                 {@code response_type} parameter. Must not be
666         *                 {@code null}.
667         * @param clientID The client identifier. Corresponds to the
668         *                 {@code client_id} parameter. Must not be
669         *                 {@code null}.
670         */
671        public AuthorizationRequest(final URI uri,
672                                    final ResponseType rt,
673                                    final ClientID clientID) {
674
675                this(uri, rt, null, clientID, null, null, null, null, null, null, false, null, null, null, null);
676        }
677
678
679        /**
680         * Creates a new authorisation request.
681         *
682         * @param uri                 The URI of the authorisation endpoint.
683         *                            May be {@code null} if the
684         *                            {@link #toHTTPRequest} method will not be
685         *                            used.
686         * @param rt                  The response type. Corresponds to the
687         *                            {@code response_type} parameter. Must not
688         *                            be {@code null}.
689         * @param rm                  The response mode. Corresponds to the
690         *                            optional {@code response_mode} parameter.
691         *                            Use of this parameter is not recommended
692         *                            unless a non-default response mode is
693         *                            requested (e.g. form_post).
694         * @param clientID            The client identifier. Corresponds to the
695         *                            {@code client_id} parameter. Must not be
696         *                            {@code null}.
697         * @param redirectURI         The redirection URI. Corresponds to the
698         *                            optional {@code redirect_uri} parameter.
699         *                            {@code null} if not specified.
700         * @param scope               The request scope. Corresponds to the
701         *                            optional {@code scope} parameter.
702         *                            {@code null} if not specified.
703         * @param state               The state. Corresponds to the recommended
704         *                            {@code state} parameter. {@code null} if
705         *                            not specified.
706         */
707        public AuthorizationRequest(final URI uri,
708                                    final ResponseType rt,
709                                    final ResponseMode rm,
710                                    final ClientID clientID,
711                                    final URI redirectURI,
712                                    final Scope scope,
713                                    final State state) {
714
715                this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null, null, null, null);
716        }
717
718
719        /**
720         * Creates a new authorisation request with extension and custom
721         * parameters.
722         *
723         * @param uri                  The URI of the authorisation endpoint.
724         *                             May be {@code null} if the
725         *                             {@link #toHTTPRequest} method will not
726         *                             be used.
727         * @param rt                   The response type. Corresponds to the
728         *                             {@code response_type} parameter. Must
729         *                             not be {@code null}, unless a request a
730         *                             request object or URI is specified.
731         * @param rm                   The response mode. Corresponds to the
732         *                             optional {@code response_mode}
733         *                             parameter. Use of this parameter is not
734         *                             recommended unless a non-default
735         *                             response mode is requested (e.g.
736         *                             form_post).
737         * @param clientID             The client identifier. Corresponds to
738         *                             the {@code client_id} parameter. Must
739         *                             not be {@code null}, unless a request
740         *                             object or URI is specified.
741         * @param redirectURI          The redirection URI. Corresponds to the
742         *                             optional {@code redirect_uri} parameter.
743         *                             {@code null} if not specified.
744         * @param scope                The request scope. Corresponds to the
745         *                             optional {@code scope} parameter.
746         *                             {@code null} if not specified.
747         * @param state                The state. Corresponds to the
748         *                             recommended {@code state} parameter.
749         *                             {@code null} if not specified.
750         * @param codeChallenge        The code challenge for PKCE,
751         *                             {@code null} if not specified.
752         * @param codeChallengeMethod  The code challenge method for PKCE,
753         *                             {@code null} if not specified.
754         * @param resources            The resource URI(s), {@code null} if not
755         *                             specified.
756         * @param includeGrantedScopes {@code true} to request incremental
757         *                             authorisation.
758         * @param requestObject        The request object. Corresponds to the
759         *                             optional {@code request} parameter. Must
760         *                             not be specified together with a request
761         *                             object URI. {@code null} if not
762         *                             specified.
763         * @param requestURI           The request object URI. Corresponds to
764         *                             the optional {@code request_uri}
765         *                             parameter. Must not be specified
766         *                             together with a request object.
767         *                             {@code null} if not specified.
768         * @param prompt               The requested prompt. Corresponds to the
769         *                             optional {@code prompt} parameter.
770         * @param customParams         Custom parameters, empty map or
771         *                             {@code null} if none.
772         */
773        public AuthorizationRequest(final URI uri,
774                                    final ResponseType rt,
775                                    final ResponseMode rm,
776                                    final ClientID clientID,
777                                    final URI redirectURI,
778                                    final Scope scope,
779                                    final State state,
780                                    final CodeChallenge codeChallenge,
781                                    final CodeChallengeMethod codeChallengeMethod,
782                                    final List<URI> resources,
783                                    final boolean includeGrantedScopes,
784                                    final JWT requestObject,
785                                    final URI requestURI,
786                                    final Prompt prompt,
787                                    final Map<String, List<String>> customParams) {
788
789                super(uri);
790
791                if (rt == null && requestObject == null && requestURI == null)
792                        throw new IllegalArgumentException("The response type must not be null");
793
794                this.rt = rt;
795
796                this.rm = rm;
797
798
799                if (clientID == null)
800                        throw new IllegalArgumentException("The client ID must not be null");
801
802                this.clientID = clientID;
803
804
805                this.redirectURI = redirectURI;
806                this.scope = scope;
807                this.state = state;
808
809                this.codeChallenge = codeChallenge;
810                this.codeChallengeMethod = codeChallengeMethod;
811                
812                if (resources != null) {
813                        for (URI resourceURI: resources) {
814                                if (! ResourceUtils.isValidResourceURI(resourceURI))
815                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
816                        }
817                }
818                
819                this.resources = resources;
820                
821                this.includeGrantedScopes = includeGrantedScopes;
822                
823                if (requestObject != null && requestURI != null)
824                        throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both");
825                
826                this.requestObject = requestObject;
827                this.requestURI = requestURI;
828                
829                if (requestObject instanceof SignedJWT) {
830                        // Make sure the "sub" claim is not set to the client_id value
831                        // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8
832                        JWTClaimsSet requestObjectClaims;
833                        try {
834                                requestObjectClaims = requestObject.getJWTClaimsSet();
835                        } catch (java.text.ParseException e) {
836                                // Should never happen
837                                throw new IllegalArgumentException("Illegal \"request\" parameter: " + e.getMessage(), e);
838                        }
839                        if (clientID.getValue().equals(requestObjectClaims.getSubject())) {
840                                throw new IllegalArgumentException("Illegal \"request\" parameter: The JWT sub (subject) claim must not equal the client_id");
841                        }
842                }
843                
844                this.prompt = prompt; // technically OpenID
845
846                if (MapUtils.isNotEmpty(customParams)) {
847                        this.customParams = Collections.unmodifiableMap(customParams);
848                } else {
849                        this.customParams = Collections.emptyMap();
850                }
851        }
852
853
854        /**
855         * Returns the registered (standard) OAuth 2.0 authorisation request
856         * parameter names.
857         *
858         * @return The registered OAuth 2.0 authorisation request parameter
859         *         names, as a unmodifiable set.
860         */
861        public static Set<String> getRegisteredParameterNames() {
862
863                return REGISTERED_PARAMETER_NAMES;
864        }
865
866
867        /**
868         * Gets the response type. Corresponds to the {@code response_type}
869         * parameter.
870         *
871         * @return The response type, may be {@code null} for a
872         *         {@link #specifiesRequestObject() JWT secured authorisation
873         *         request} with a {@link #getRequestObject() request} or
874         *         {@link #getRequestURI() request_uri} parameter.
875         */
876        public ResponseType getResponseType() {
877        
878                return rt;
879        }
880
881
882        /**
883         * Gets the optional response mode. Corresponds to the optional
884         * {@code response_mode} parameter.
885         *
886         * @return The response mode, {@code null} if not specified.
887         */
888        public ResponseMode getResponseMode() {
889
890                return rm;
891        }
892
893
894        /**
895         * Returns the implied response mode, determined by the optional
896         * {@code response_mode} parameter, and if that isn't specified, by
897         * the {@code response_type}.
898         *
899         * @return The implied response mode.
900         */
901        public ResponseMode impliedResponseMode() {
902                
903                return ResponseMode.resolve(rm, rt);
904        }
905
906
907        /**
908         * Gets the client identifier. Corresponds to the {@code client_id} 
909         * parameter.
910         *
911         * @return The client identifier.
912         */
913        public ClientID getClientID() {
914        
915                return clientID;
916        }
917
918
919        /**
920         * Gets the redirection URI. Corresponds to the optional 
921         * {@code redirection_uri} parameter.
922         *
923         * @return The redirection URI, {@code null} if not specified.
924         */
925        public URI getRedirectionURI() {
926        
927                return redirectURI;
928        }
929        
930        
931        /**
932         * Gets the scope. Corresponds to the optional {@code scope} parameter.
933         *
934         * @return The scope, {@code null} if not specified.
935         */
936        public Scope getScope() {
937        
938                return scope;
939        }
940        
941        
942        /**
943         * Gets the state. Corresponds to the recommended {@code state} 
944         * parameter.
945         *
946         * @return The state, {@code null} if not specified.
947         */
948        public State getState() {
949        
950                return state;
951        }
952
953
954        /**
955         * Returns the code challenge for PKCE.
956         *
957         * @return The code challenge, {@code null} if not specified.
958         */
959        public CodeChallenge getCodeChallenge() {
960
961                return codeChallenge;
962        }
963
964
965        /**
966         * Returns the code challenge method for PKCE.
967         *
968         * @return The code challenge method, {@code null} if not specified.
969         */
970        public CodeChallengeMethod getCodeChallengeMethod() {
971
972                return codeChallengeMethod;
973        }
974        
975        
976        /**
977         * Returns the resource server URI.
978         *
979         * @return The resource URI(s), {@code null} if not specified.
980         */
981        public List<URI> getResources() {
982                
983                return resources;
984        }
985        
986        
987        /**
988         * Returns {@code true} if incremental authorisation is requested.
989         *
990         * @return {@code true} if incremental authorisation is requested,
991         *         else {@code false}.
992         */
993        public boolean includeGrantedScopes() {
994                
995                return includeGrantedScopes;
996        }
997        
998        
999        /**
1000         * Gets the request object. Corresponds to the optional {@code request}
1001         * parameter.
1002         *
1003         * @return The request object, {@code null} if not specified.
1004         */
1005        public JWT getRequestObject() {
1006                
1007                return requestObject;
1008        }
1009        
1010        
1011        /**
1012         * Gets the request object URI. Corresponds to the optional
1013         * {@code request_uri} parameter.
1014         *
1015         * @return The request object URI, {@code null} if not specified.
1016         */
1017        public URI getRequestURI() {
1018                
1019                return requestURI;
1020        }
1021        
1022        
1023        /**
1024         * Returns {@code true} if this is a JWT secured authentication
1025         * request.
1026         *
1027         * @return {@code true} if a request object via a {@code request} or
1028         *         {@code request_uri} parameter is specified, else
1029         *         {@code false}.
1030         */
1031        public boolean specifiesRequestObject() {
1032                
1033                return requestObject != null || requestURI != null;
1034        }
1035        
1036        
1037        /**
1038         * Gets the requested prompt. Corresponds to the optional
1039         * {@code prompt} parameter.
1040         *
1041         * @return The requested prompt, {@code null} if not specified.
1042         */
1043        public Prompt getPrompt() {
1044                
1045                return prompt;
1046        }
1047        
1048        
1049        /**
1050         * Returns the additional custom parameters.
1051         *
1052         * @return The additional custom parameters as a unmodifiable map,
1053         *         empty map if none.
1054         */
1055        public Map<String,List<String>> getCustomParameters () {
1056
1057                return customParams;
1058        }
1059        
1060        
1061        /**
1062         * Returns the specified custom parameter.
1063         *
1064         * @param name The parameter name. Must not be {@code null}.
1065         *
1066         * @return The parameter value(s), {@code null} if not specified.
1067         */
1068        public List<String> getCustomParameter(final String name) {
1069
1070                return customParams.get(name);
1071        }
1072        
1073        
1074        /**
1075         * Returns the URI query parameters for this authorisation request.
1076         * Query parameters which are part of the authorisation endpoint are
1077         * not included.
1078         *
1079         * <p>Example parameters:
1080         *
1081         * <pre>
1082         * response_type = code
1083         * client_id     = s6BhdRkqt3
1084         * state         = xyz
1085         * redirect_uri  = https://client.example.com/cb
1086         * </pre>
1087         * 
1088         * @return The parameters.
1089         */
1090        public Map<String,List<String>> toParameters() {
1091                
1092                // Put custom params first, so they may be overwritten by std params
1093                Map<String, List<String>> params = new LinkedHashMap<>(customParams);
1094                
1095                params.put("client_id", Collections.singletonList(clientID.getValue()));
1096                
1097                if (rt != null)
1098                        params.put("response_type", Collections.singletonList(rt.toString()));
1099
1100                if (rm != null)
1101                        params.put("response_mode", Collections.singletonList(rm.getValue()));
1102
1103                if (redirectURI != null)
1104                        params.put("redirect_uri", Collections.singletonList(redirectURI.toString()));
1105
1106                if (scope != null)
1107                        params.put("scope", Collections.singletonList(scope.toString()));
1108                
1109                if (state != null)
1110                        params.put("state", Collections.singletonList(state.getValue()));
1111
1112                if (codeChallenge != null) {
1113                        params.put("code_challenge", Collections.singletonList(codeChallenge.getValue()));
1114
1115                        if (codeChallengeMethod != null) {
1116                                params.put("code_challenge_method", Collections.singletonList(codeChallengeMethod.getValue()));
1117                        }
1118                }
1119                
1120                if (includeGrantedScopes)
1121                        params.put("include_granted_scopes", Collections.singletonList("true"));
1122                
1123                if (resources != null) {
1124                        List<String> resourceValues = new LinkedList<>();
1125                        for (URI resourceURI: resources) {
1126                                if (resourceURI != null) {
1127                                        resourceValues.add(resourceURI.toString());
1128                                }
1129                        }
1130                        params.put("resource", resourceValues);
1131                }
1132                
1133                if (requestObject != null) {
1134                        try {
1135                                params.put("request", Collections.singletonList(requestObject.serialize()));
1136                                
1137                        } catch (IllegalStateException e) {
1138                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1139                        }
1140                }
1141                
1142                if (requestURI != null)
1143                        params.put("request_uri", Collections.singletonList(requestURI.toString()));
1144                
1145                if (prompt != null)
1146                        params.put("prompt", Collections.singletonList(prompt.toString()));
1147
1148                return params;
1149        }
1150        
1151        
1152        /**
1153         * Returns the parameters for this authorisation request as a JSON Web
1154         * Token (JWT) claims set. Intended for creating a request object.
1155         *
1156         * @return The parameters as JWT claim set.
1157         */
1158        public JWTClaimsSet toJWTClaimsSet() {
1159                
1160                if (specifiesRequestObject()) {
1161                        throw new IllegalStateException("Cannot create nested JWT secured authorization request");
1162                }
1163                
1164                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
1165        }
1166        
1167        
1168        /**
1169         * Returns the URI query string for this authorisation request.
1170         *
1171         * <p>Note that the '?' character preceding the query string in an URI
1172         * is not included in the returned string.
1173         *
1174         * <p>Example URI query string:
1175         *
1176         * <pre>
1177         * response_type=code
1178         * &amp;client_id=s6BhdRkqt3
1179         * &amp;state=xyz
1180         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1181         * </pre>
1182         * 
1183         * @return The URI query string.
1184         */
1185        public String toQueryString() {
1186                
1187                Map<String, List<String>> params = new HashMap<>();
1188                if (getEndpointURI() != null) {
1189                        params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery()));
1190                }
1191                params.putAll(toParameters());
1192                
1193                return URLUtils.serializeParameters(params);
1194        }
1195
1196
1197        /**
1198         * Returns the complete URI representation for this authorisation
1199         * request, consisting of the {@link #getEndpointURI authorization
1200         * endpoint URI} with the {@link #toQueryString query string} appended.
1201         *
1202         * <p>Example URI:
1203         *
1204         * <pre>
1205         * https://server.example.com/authorize?
1206         * response_type=code
1207         * &amp;client_id=s6BhdRkqt3
1208         * &amp;state=xyz
1209         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1210         * </pre>
1211         *
1212         * @return The URI representation.
1213         */
1214        public URI toURI() {
1215
1216                if (getEndpointURI() == null)
1217                        throw new SerializeException("The authorization endpoint URI is not specified");
1218                
1219                StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString());
1220                sb.append('?');
1221                sb.append(toQueryString());
1222                try {
1223                        return new URI(sb.toString());
1224                } catch (URISyntaxException e) {
1225                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
1226                }
1227        }
1228        
1229        
1230        /**
1231         * Returns the matching HTTP request.
1232         *
1233         * @param method The HTTP request method which can be GET or POST. Must
1234         *               not be {@code null}.
1235         *
1236         * @return The HTTP request.
1237         */
1238        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
1239                
1240                if (getEndpointURI() == null)
1241                        throw new SerializeException("The endpoint URI is not specified");
1242                
1243                HTTPRequest httpRequest;
1244
1245                URL endpointURL;
1246
1247                try {
1248                        endpointURL = getEndpointURI().toURL();
1249
1250                } catch (MalformedURLException e) {
1251
1252                        throw new SerializeException(e.getMessage(), e);
1253                }
1254                
1255                if (method.equals(HTTPRequest.Method.GET)) {
1256
1257                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
1258
1259                } else if (method.equals(HTTPRequest.Method.POST)) {
1260
1261                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
1262
1263                } else {
1264
1265                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
1266                }
1267                
1268                httpRequest.setQuery(toQueryString());
1269                
1270                return httpRequest;
1271        }
1272        
1273        
1274        @Override
1275        public HTTPRequest toHTTPRequest() {
1276        
1277                return toHTTPRequest(HTTPRequest.Method.GET);
1278        }
1279
1280
1281        /**
1282         * Parses an authorisation request from the specified URI query
1283         * parameters.
1284         *
1285         * <p>Example parameters:
1286         *
1287         * <pre>
1288         * response_type = code
1289         * client_id     = s6BhdRkqt3
1290         * state         = xyz
1291         * redirect_uri  = https://client.example.com/cb
1292         * </pre>
1293         *
1294         * @param params The parameters. Must not be {@code null}.
1295         *
1296         * @return The authorisation request.
1297         *
1298         * @throws ParseException If the parameters couldn't be parsed to an
1299         *                        authorisation request.
1300         */
1301        public static AuthorizationRequest parse(final Map<String,List<String>> params)
1302                throws ParseException {
1303
1304                return parse(null, params);
1305        }
1306
1307
1308        /**
1309         * Parses an authorisation request from the specified URI and query
1310         * parameters.
1311         *
1312         * <p>Example parameters:
1313         *
1314         * <pre>
1315         * response_type = code
1316         * client_id     = s6BhdRkqt3
1317         * state         = xyz
1318         * redirect_uri  = https://client.example.com/cb
1319         * </pre>
1320         *
1321         * @param uri    The URI of the authorisation endpoint. May be
1322         *               {@code null} if the {@link #toHTTPRequest()} method
1323         *               will not be used.
1324         * @param params The parameters. Must not be {@code null}.
1325         *
1326         * @return The authorisation request.
1327         *
1328         * @throws ParseException If the parameters couldn't be parsed to an
1329         *                        authorisation request.
1330         */
1331        public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params)
1332                throws ParseException {
1333                
1334                // Parse response_mode, response_type, client_id, redirect_uri and state first,
1335                // needed if parsing results in a error response
1336                final ClientID clientID;
1337                URI redirectURI = null;
1338                State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state"));
1339                ResponseMode rm = null;
1340                ResponseType rt = null;
1341                
1342                // Optional response_mode
1343                String v = MultivaluedMapUtils.getFirstValue(params, "response_mode");
1344                if (StringUtils.isNotBlank(v)) {
1345                        rm = new ResponseMode(v);
1346                }
1347                
1348                // Mandatory client_id
1349                v = MultivaluedMapUtils.getFirstValue(params, "client_id");
1350                if (StringUtils.isBlank(v)) {
1351                        // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1352                        String msg = "Missing \"client_id\" parameter";
1353                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1354                }
1355                clientID = new ClientID(v);
1356                
1357                // Optional redirect_uri
1358                v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri");
1359                if (StringUtils.isNotBlank(v)) {
1360                        try {
1361                                redirectURI = new URI(v);
1362                        } catch (URISyntaxException e) {
1363                                // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1364                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
1365                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1366                        }
1367                }
1368                
1369                // Mandatory response_type, unless in JAR
1370                v = MultivaluedMapUtils.getFirstValue(params, "response_type");
1371                if (StringUtils.isNotBlank(v)) {
1372                        try {
1373                                rt = ResponseType.parse(v);
1374                        } catch (ParseException e) {
1375                                // Only cause
1376                                String msg = "Invalid \"response_type\" parameter";
1377                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1378                                        clientID, redirectURI, rm, state, e);
1379                        }
1380                }
1381                
1382                // Check for a JAR in request or request_uri parameters
1383                v = MultivaluedMapUtils.getFirstValue(params, "request_uri");
1384                URI requestURI = null;
1385                if (StringUtils.isNotBlank(v)) {
1386                        try {
1387                                requestURI = new URI(v);
1388                        } catch (URISyntaxException e) {
1389                                String msg = "Invalid \"request_uri\" parameter: " + e.getMessage();
1390                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1391                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1392                        }
1393                }
1394                
1395                v = MultivaluedMapUtils.getFirstValue(params, "request");
1396                
1397                JWT requestObject = null;
1398                
1399                if (StringUtils.isNotBlank(v)) {
1400                        
1401                        // request_object and request_uri must not be present at the same time
1402                        if (requestURI != null) {
1403                                String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters";
1404                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1405                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1406                        }
1407                        
1408                        try {
1409                                requestObject = JWTParser.parse(v);
1410                                
1411                                if (requestObject instanceof SignedJWT) {
1412                                        // Make sure the "sub" claim is not set to the client_id value
1413                                        // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8
1414                                        JWTClaimsSet requestObjectClaims = requestObject.getJWTClaimsSet();
1415                                        if (clientID.getValue().equals(requestObjectClaims.getSubject())) {
1416                                                throw new java.text.ParseException("The JWT sub (subject) claim must not equal the client_id", 0);
1417                                        }
1418                                }
1419                                
1420                        } catch (java.text.ParseException e) {
1421                                String msg = "Invalid \"request\" parameter: " + e.getMessage();
1422                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1423                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1424                        }
1425                }
1426
1427                // Response type mandatory, unless in JAR
1428                if (rt == null && requestObject == null && requestURI == null) {
1429                        String msg = "Missing \"response_type\" parameter";
1430                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1431                                clientID, redirectURI, ResponseMode.resolve(rm, null), state, null);
1432                }
1433
1434
1435                // Parse optional scope
1436                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1437
1438                Scope scope = null;
1439
1440                if (StringUtils.isNotBlank(v))
1441                        scope = Scope.parse(v);
1442
1443
1444                // Parse optional code challenge and method for PKCE
1445                CodeChallenge codeChallenge = null;
1446                CodeChallengeMethod codeChallengeMethod = null;
1447
1448                v = MultivaluedMapUtils.getFirstValue(params, "code_challenge");
1449
1450                if (StringUtils.isNotBlank(v))
1451                        codeChallenge = CodeChallenge.parse(v);
1452
1453                if (codeChallenge != null) {
1454
1455                        v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method");
1456
1457                        if (StringUtils.isNotBlank(v))
1458                                codeChallengeMethod = CodeChallengeMethod.parse(v);
1459                }
1460                
1461                List<URI> resources = null;
1462                
1463                List<String> vList = params.get("resource");
1464                
1465                if (vList != null) {
1466                        
1467                        resources = new LinkedList<>();
1468                        
1469                        for (String uriValue: vList) {
1470                                
1471                                if (uriValue == null)
1472                                        continue;
1473                                
1474                                String errMsg = "Invalid \"resource\" parameter: Must be an absolute URI and with no query or fragment: " + uriValue;
1475                                
1476                                URI resourceURI;
1477                                try {
1478                                        resourceURI = new URI(uriValue);
1479                                } catch (URISyntaxException e) {
1480                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1481                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1482                                }
1483                                
1484                                if (! ResourceUtils.isValidResourceURI(resourceURI)) {
1485                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1486                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1487                                }
1488                                
1489                                resources.add(resourceURI);
1490                        }
1491                }
1492                
1493                boolean includeGrantedScopes = false;
1494                v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes");
1495                if ("true".equals(v)) {
1496                        includeGrantedScopes = true;
1497                }
1498                
1499                Prompt prompt;
1500                try {
1501                        prompt = Prompt.parse(MultivaluedMapUtils.getFirstValue(params, "prompt"));
1502                        
1503                } catch (ParseException e) {
1504                        String msg = "Invalid \"prompt\" parameter: " + e.getMessage();
1505                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1506                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1507                }
1508                
1509                // Parse custom parameters
1510                Map<String,List<String>> customParams = null;
1511
1512                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1513
1514                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1515                                // We have a custom parameter
1516                                if (customParams == null) {
1517                                        customParams = new HashMap<>();
1518                                }
1519                                customParams.put(p.getKey(), p.getValue());
1520                        }
1521                }
1522
1523
1524                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
1525                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
1526                        requestObject, requestURI,
1527                        prompt,
1528                        customParams);
1529        }
1530
1531
1532        /**
1533         * Parses an authorisation request from the specified URI query string.
1534         *
1535         * <p>Example URI query string:
1536         *
1537         * <pre>
1538         * response_type=code
1539         * &amp;client_id=s6BhdRkqt3
1540         * &amp;state=xyz
1541         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1542         * </pre>
1543         *
1544         * @param query The URI query string. Must not be {@code null}.
1545         *
1546         * @return The authorisation request.
1547         *
1548         * @throws ParseException If the query string couldn't be parsed to an
1549         *                        authorisation request.
1550         */
1551        public static AuthorizationRequest parse(final String query)
1552                throws ParseException {
1553
1554                return parse(null, URLUtils.parseParameters(query));
1555        }
1556        
1557        
1558        /**
1559         * Parses an authorisation request from the specified URI and query
1560         * string.
1561         *
1562         * <p>Example URI query string:
1563         *
1564         * <pre>
1565         * response_type=code
1566         * &amp;client_id=s6BhdRkqt3
1567         * &amp;state=xyz
1568         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1569         * </pre>
1570         *
1571         * @param uri   The URI of the authorisation endpoint. May be 
1572         *              {@code null} if the {@link #toHTTPRequest()} method
1573         *              will not be used.
1574         * @param query The URI query string. Must not be {@code null}.
1575         *
1576         * @return The authorisation request.
1577         *
1578         * @throws ParseException If the query string couldn't be parsed to an 
1579         *                        authorisation request.
1580         */
1581        public static AuthorizationRequest parse(final URI uri, final String query)
1582                throws ParseException {
1583        
1584                return parse(uri, URLUtils.parseParameters(query));
1585        }
1586
1587
1588        /**
1589         * Parses an authorisation request from the specified URI.
1590         *
1591         * <p>Example URI:
1592         *
1593         * <pre>
1594         * https://server.example.com/authorize?
1595         * response_type=code
1596         * &amp;client_id=s6BhdRkqt3
1597         * &amp;state=xyz
1598         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1599         * </pre>
1600         *
1601         * @param uri The URI. Must not be {@code null}.
1602         *
1603         * @return The authorisation request.
1604         *
1605         * @throws ParseException If the URI couldn't be parsed to an
1606         *                        authorisation request.
1607         */
1608        public static AuthorizationRequest parse(final URI uri)
1609                throws ParseException {
1610
1611                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1612        }
1613        
1614        
1615        /**
1616         * Parses an authorisation request from the specified HTTP request.
1617         *
1618         * <p>Example HTTP request (GET):
1619         *
1620         * <pre>
1621         * https://server.example.com/authorize?
1622         * response_type=code
1623         * &amp;client_id=s6BhdRkqt3
1624         * &amp;state=xyz
1625         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1626         * </pre>
1627         *
1628         * @param httpRequest The HTTP request. Must not be {@code null}.
1629         *
1630         * @return The authorisation request.
1631         *
1632         * @throws ParseException If the HTTP request couldn't be parsed to an 
1633         *                        authorisation request.
1634         */
1635        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
1636                throws ParseException {
1637                
1638                String query = httpRequest.getQuery();
1639                
1640                if (query == null)
1641                        throw new ParseException("Missing URI query string");
1642
1643                try {
1644                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
1645
1646                } catch (URISyntaxException e) {
1647
1648                        throw new ParseException(e.getMessage(), e);
1649                }
1650        }
1651}