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