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.
519                 *
520                 * @param resource The resource URI, {@code null} if not
521                 *                 specified.
522                 *
523                 * @return This builder.
524                 */
525                public Builder resource(final URI resource) {
526                        if (resource != null) {
527                                this.resources = Collections.singletonList(resource);
528                        } else {
529                                this.resources = null;
530                        }
531                        return this;
532                }
533                
534                
535                /**
536                 * Sets the resource server URI(s).
537                 *
538                 * @param resources The resource URI(s), {@code null} if not
539                 *                  specified.
540                 *
541                 * @return This builder.
542                 */
543                public Builder resources(final URI ... resources) {
544                        if (resources != null) {
545                                this.resources = Arrays.asList(resources);
546                        } else {
547                                this.resources = null;
548                        }
549                        return this;
550                }
551                
552                
553                /**
554                 * Requests incremental authorisation.
555                 *
556                 * @param includeGrantedScopes {@code true} to request
557                 *                             incremental authorisation.
558                 *
559                 * @return This builder.
560                 */
561                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
562                        
563                        this.includeGrantedScopes = includeGrantedScopes;
564                        return this;
565                }
566                
567                
568                /**
569                 * Sets the request object. Corresponds to the optional
570                 * {@code request} parameter. Must not be specified together
571                 * with a request object URI.
572                 *
573                 * @param requestObject The request object, {@code null} if not
574                 *                      specified.
575                 *
576                 * @return This builder.
577                 */
578                public Builder requestObject(final JWT requestObject) {
579                        
580                        this.requestObject = requestObject;
581                        return this;
582                }
583                
584                
585                /**
586                 * Sets the request object URI. Corresponds to the optional
587                 * {@code request_uri} parameter. Must not be specified
588                 * together with a request object.
589                 *
590                 * @param requestURI The request object URI, {@code null} if
591                 *                   not specified.
592                 *
593                 * @return This builder.
594                 */
595                public Builder requestURI(final URI requestURI) {
596                        
597                        this.requestURI = requestURI;
598                        return this;
599                }
600                
601                
602                /**
603                 * Sets the requested prompt. Corresponds to the optional
604                 * {@code prompt} parameter.
605                 *
606                 * @param prompt The requested prompt, {@code null} if not
607                 *               specified.
608                 *
609                 * @return This builder.
610                 */
611                public Builder prompt(final Prompt prompt) {
612                        
613                        this.prompt = prompt;
614                        return this;
615                }
616                
617                
618                /**
619                 * Sets a custom parameter.
620                 *
621                 * @param name   The parameter name. Must not be {@code null}.
622                 * @param values The parameter values, {@code null} if not
623                 *               specified.
624                 *
625                 * @return This builder.
626                 */
627                public Builder customParameter(final String name, final String ... values) {
628
629                        if (values == null || values.length == 0) {
630                                customParams.remove(name);
631                        } else {
632                                customParams.put(name, Arrays.asList(values));
633                        }
634                        
635                        return this;
636                }
637
638
639                /**
640                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
641                 * request is intended.
642                 *
643                 * @param uri The endpoint URI, {@code null} if not specified.
644                 *
645                 * @return This builder.
646                 */
647                public Builder endpointURI(final URI uri) {
648
649                        this.uri = uri;
650                        return this;
651                }
652
653
654                /**
655                 * Builds a new authorisation request.
656                 *
657                 * @return The authorisation request.
658                 */
659                public AuthorizationRequest build() {
660
661                        try {
662                                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
663                                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
664                                        requestObject, requestURI,
665                                        prompt,
666                                        customParams);
667                        } catch (IllegalArgumentException e) {
668                                throw new IllegalStateException(e.getMessage(), e);
669                        }
670                }
671        }
672
673
674        /**
675         * Creates a new minimal authorisation request.
676         *
677         * @param uri      The URI of the authorisation endpoint. May be
678         *                 {@code null} if the {@link #toHTTPRequest} method
679         *                 will not be used.
680         * @param rt       The response type. Corresponds to the
681         *                 {@code response_type} parameter. Must not be
682         *                 {@code null}.
683         * @param clientID The client identifier. Corresponds to the
684         *                 {@code client_id} parameter. Must not be
685         *                 {@code null}.
686         */
687        public AuthorizationRequest(final URI uri,
688                                    final ResponseType rt,
689                                    final ClientID clientID) {
690
691                this(uri, rt, null, clientID, null, null, null, null, null, null, false, null, null, null, null);
692        }
693
694
695        /**
696         * Creates a new authorisation request.
697         *
698         * @param uri                 The URI of the authorisation endpoint.
699         *                            May be {@code null} if the
700         *                            {@link #toHTTPRequest} method will not be
701         *                            used.
702         * @param rt                  The response type. Corresponds to the
703         *                            {@code response_type} parameter. Must not
704         *                            be {@code null}.
705         * @param rm                  The response mode. Corresponds to the
706         *                            optional {@code response_mode} parameter.
707         *                            Use of this parameter is not recommended
708         *                            unless a non-default response mode is
709         *                            requested (e.g. form_post).
710         * @param clientID            The client identifier. Corresponds to the
711         *                            {@code client_id} parameter. Must not be
712         *                            {@code null}.
713         * @param redirectURI         The redirection URI. Corresponds to the
714         *                            optional {@code redirect_uri} parameter.
715         *                            {@code null} if not specified.
716         * @param scope               The request scope. Corresponds to the
717         *                            optional {@code scope} parameter.
718         *                            {@code null} if not specified.
719         * @param state               The state. Corresponds to the recommended
720         *                            {@code state} parameter. {@code null} if
721         *                            not specified.
722         */
723        public AuthorizationRequest(final URI uri,
724                                    final ResponseType rt,
725                                    final ResponseMode rm,
726                                    final ClientID clientID,
727                                    final URI redirectURI,
728                                    final Scope scope,
729                                    final State state) {
730
731                this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null, null, null, null);
732        }
733
734
735        /**
736         * Creates a new authorisation request with extension and custom
737         * parameters.
738         *
739         * @param uri                  The URI of the authorisation endpoint.
740         *                             May be {@code null} if the
741         *                             {@link #toHTTPRequest} method will not
742         *                             be used.
743         * @param rt                   The response type. Corresponds to the
744         *                             {@code response_type} parameter. Must
745         *                             not be {@code null}, unless a request a
746         *                             request object or URI is specified.
747         * @param rm                   The response mode. Corresponds to the
748         *                             optional {@code response_mode}
749         *                             parameter. Use of this parameter is not
750         *                             recommended unless a non-default
751         *                             response mode is requested (e.g.
752         *                             form_post).
753         * @param clientID             The client identifier. Corresponds to
754         *                             the {@code client_id} parameter. Must
755         *                             not be {@code null}, unless a request
756         *                             object or URI is specified.
757         * @param redirectURI          The redirection URI. Corresponds to the
758         *                             optional {@code redirect_uri} parameter.
759         *                             {@code null} if not specified.
760         * @param scope                The request scope. Corresponds to the
761         *                             optional {@code scope} parameter.
762         *                             {@code null} if not specified.
763         * @param state                The state. Corresponds to the
764         *                             recommended {@code state} parameter.
765         *                             {@code null} if not specified.
766         * @param codeChallenge        The code challenge for PKCE,
767         *                             {@code null} if not specified.
768         * @param codeChallengeMethod  The code challenge method for PKCE,
769         *                             {@code null} if not specified.
770         * @param resources            The resource URI(s), {@code null} if not
771         *                             specified.
772         * @param includeGrantedScopes {@code true} to request incremental
773         *                             authorisation.
774         * @param requestObject        The request object. Corresponds to the
775         *                             optional {@code request} parameter. Must
776         *                             not be specified together with a request
777         *                             object URI. {@code null} if not
778         *                             specified.
779         * @param requestURI           The request object URI. Corresponds to
780         *                             the optional {@code request_uri}
781         *                             parameter. Must not be specified
782         *                             together with a request object.
783         *                             {@code null} if not specified.
784         * @param prompt               The requested prompt. Corresponds to the
785         *                             optional {@code prompt} parameter.
786         * @param customParams         Custom parameters, empty map or
787         *                             {@code null} if none.
788         */
789        public AuthorizationRequest(final URI uri,
790                                    final ResponseType rt,
791                                    final ResponseMode rm,
792                                    final ClientID clientID,
793                                    final URI redirectURI,
794                                    final Scope scope,
795                                    final State state,
796                                    final CodeChallenge codeChallenge,
797                                    final CodeChallengeMethod codeChallengeMethod,
798                                    final List<URI> resources,
799                                    final boolean includeGrantedScopes,
800                                    final JWT requestObject,
801                                    final URI requestURI,
802                                    final Prompt prompt,
803                                    final Map<String, List<String>> customParams) {
804
805                super(uri);
806
807                if (rt == null && requestObject == null && requestURI == null)
808                        throw new IllegalArgumentException("The response type must not be null");
809
810                this.rt = rt;
811
812                this.rm = rm;
813
814
815                if (clientID == null)
816                        throw new IllegalArgumentException("The client ID must not be null");
817
818                this.clientID = clientID;
819
820
821                this.redirectURI = redirectURI;
822                this.scope = scope;
823                this.state = state;
824
825                this.codeChallenge = codeChallenge;
826                this.codeChallengeMethod = codeChallengeMethod;
827                
828                if (resources != null) {
829                        for (URI resourceURI: resources) {
830                                if (! ResourceUtils.isValidResourceURI(resourceURI))
831                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
832                        }
833                }
834                
835                this.resources = resources;
836                
837                this.includeGrantedScopes = includeGrantedScopes;
838                
839                if (requestObject != null && requestURI != null)
840                        throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both");
841                
842                this.requestObject = requestObject;
843                this.requestURI = requestURI;
844                
845                if (requestObject instanceof SignedJWT) {
846                        // Make sure the "sub" claim is not set to the client_id value
847                        // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8
848                        JWTClaimsSet requestObjectClaims;
849                        try {
850                                requestObjectClaims = requestObject.getJWTClaimsSet();
851                        } catch (java.text.ParseException e) {
852                                // Should never happen
853                                throw new IllegalArgumentException("Illegal request parameter: " + e.getMessage(), e);
854                        }
855                        if (clientID.getValue().equals(requestObjectClaims.getSubject())) {
856                                throw new IllegalArgumentException("Illegal request parameter: The JWT sub (subject) claim must not equal the client_id");
857                        }
858                }
859                
860                this.prompt = prompt; // technically OpenID
861
862                if (MapUtils.isNotEmpty(customParams)) {
863                        this.customParams = Collections.unmodifiableMap(customParams);
864                } else {
865                        this.customParams = Collections.emptyMap();
866                }
867        }
868
869
870        /**
871         * Returns the registered (standard) OAuth 2.0 authorisation request
872         * parameter names.
873         *
874         * @return The registered OAuth 2.0 authorisation request parameter
875         *         names, as a unmodifiable set.
876         */
877        public static Set<String> getRegisteredParameterNames() {
878
879                return REGISTERED_PARAMETER_NAMES;
880        }
881
882
883        /**
884         * Gets the response type. Corresponds to the {@code response_type}
885         * parameter.
886         *
887         * @return The response type, may be {@code null} for a
888         *         {@link #specifiesRequestObject() JWT secured authorisation
889         *         request} with a {@link #getRequestObject() request} or
890         *         {@link #getRequestURI() request_uri} parameter.
891         */
892        public ResponseType getResponseType() {
893        
894                return rt;
895        }
896
897
898        /**
899         * Gets the optional response mode. Corresponds to the optional
900         * {@code response_mode} parameter.
901         *
902         * @return The response mode, {@code null} if not specified.
903         */
904        public ResponseMode getResponseMode() {
905
906                return rm;
907        }
908
909
910        /**
911         * Returns the implied response mode, determined by the optional
912         * {@code response_mode} parameter, and if that isn't specified, by
913         * the {@code response_type}.
914         *
915         * <p>If the {@link ResponseMode#JWT jwt} response mode shortcut from
916         * JARM is explicitly requested expands it to
917         * {@link ResponseMode#QUERY_JWT query.jwt} or
918         * {@link ResponseMode#FRAGMENT_JWT fragment.jwt} depending on the
919         * response type ({@code response_type}).
920         *
921         * @return The implied response mode.
922         */
923        public ResponseMode impliedResponseMode() {
924                
925                return ResponseMode.resolve(rm, rt);
926        }
927
928
929        /**
930         * Gets the client identifier. Corresponds to the {@code client_id} 
931         * parameter.
932         *
933         * @return The client identifier.
934         */
935        public ClientID getClientID() {
936        
937                return clientID;
938        }
939
940
941        /**
942         * Gets the redirection URI. Corresponds to the optional 
943         * {@code redirection_uri} parameter.
944         *
945         * @return The redirection URI, {@code null} if not specified.
946         */
947        public URI getRedirectionURI() {
948        
949                return redirectURI;
950        }
951        
952        
953        /**
954         * Gets the scope. Corresponds to the optional {@code scope} parameter.
955         *
956         * @return The scope, {@code null} if not specified.
957         */
958        public Scope getScope() {
959        
960                return scope;
961        }
962        
963        
964        /**
965         * Gets the state. Corresponds to the recommended {@code state} 
966         * parameter.
967         *
968         * @return The state, {@code null} if not specified.
969         */
970        public State getState() {
971        
972                return state;
973        }
974
975
976        /**
977         * Returns the code challenge for PKCE.
978         *
979         * @return The code challenge, {@code null} if not specified.
980         */
981        public CodeChallenge getCodeChallenge() {
982
983                return codeChallenge;
984        }
985
986
987        /**
988         * Returns the code challenge method for PKCE.
989         *
990         * @return The code challenge method, {@code null} if not specified.
991         */
992        public CodeChallengeMethod getCodeChallengeMethod() {
993
994                return codeChallengeMethod;
995        }
996        
997        
998        /**
999         * Returns the resource server URI.
1000         *
1001         * @return The resource URI(s), {@code null} if not specified.
1002         */
1003        public List<URI> getResources() {
1004                
1005                return resources;
1006        }
1007        
1008        
1009        /**
1010         * Returns {@code true} if incremental authorisation is requested.
1011         *
1012         * @return {@code true} if incremental authorisation is requested,
1013         *         else {@code false}.
1014         */
1015        public boolean includeGrantedScopes() {
1016                
1017                return includeGrantedScopes;
1018        }
1019        
1020        
1021        /**
1022         * Gets the request object. Corresponds to the optional {@code request}
1023         * parameter.
1024         *
1025         * @return The request object, {@code null} if not specified.
1026         */
1027        public JWT getRequestObject() {
1028                
1029                return requestObject;
1030        }
1031        
1032        
1033        /**
1034         * Gets the request object URI. Corresponds to the optional
1035         * {@code request_uri} parameter.
1036         *
1037         * @return The request object URI, {@code null} if not specified.
1038         */
1039        public URI getRequestURI() {
1040                
1041                return requestURI;
1042        }
1043        
1044        
1045        /**
1046         * Returns {@code true} if this is a JWT secured authentication
1047         * request.
1048         *
1049         * @return {@code true} if a request object via a {@code request} or
1050         *         {@code request_uri} parameter is specified, else
1051         *         {@code false}.
1052         */
1053        public boolean specifiesRequestObject() {
1054                
1055                return requestObject != null || requestURI != null;
1056        }
1057        
1058        
1059        /**
1060         * Gets the requested prompt. Corresponds to the optional
1061         * {@code prompt} parameter.
1062         *
1063         * @return The requested prompt, {@code null} if not specified.
1064         */
1065        public Prompt getPrompt() {
1066                
1067                return prompt;
1068        }
1069        
1070        
1071        /**
1072         * Returns the additional custom parameters.
1073         *
1074         * @return The additional custom parameters as a unmodifiable map,
1075         *         empty map if none.
1076         */
1077        public Map<String,List<String>> getCustomParameters () {
1078
1079                return customParams;
1080        }
1081        
1082        
1083        /**
1084         * Returns the specified custom parameter.
1085         *
1086         * @param name The parameter name. Must not be {@code null}.
1087         *
1088         * @return The parameter value(s), {@code null} if not specified.
1089         */
1090        public List<String> getCustomParameter(final String name) {
1091
1092                return customParams.get(name);
1093        }
1094        
1095        
1096        /**
1097         * Returns the URI query parameters for this authorisation request.
1098         * Query parameters which are part of the authorisation endpoint are
1099         * not included.
1100         *
1101         * <p>Example parameters:
1102         *
1103         * <pre>
1104         * response_type = code
1105         * client_id     = s6BhdRkqt3
1106         * state         = xyz
1107         * redirect_uri  = https://client.example.com/cb
1108         * </pre>
1109         * 
1110         * @return The parameters.
1111         */
1112        public Map<String,List<String>> toParameters() {
1113                
1114                // Put custom params first, so they may be overwritten by std params
1115                Map<String, List<String>> params = new LinkedHashMap<>(customParams);
1116                
1117                params.put("client_id", Collections.singletonList(clientID.getValue()));
1118                
1119                if (rt != null)
1120                        params.put("response_type", Collections.singletonList(rt.toString()));
1121
1122                if (rm != null)
1123                        params.put("response_mode", Collections.singletonList(rm.getValue()));
1124
1125                if (redirectURI != null)
1126                        params.put("redirect_uri", Collections.singletonList(redirectURI.toString()));
1127
1128                if (scope != null)
1129                        params.put("scope", Collections.singletonList(scope.toString()));
1130                
1131                if (state != null)
1132                        params.put("state", Collections.singletonList(state.getValue()));
1133
1134                if (codeChallenge != null) {
1135                        params.put("code_challenge", Collections.singletonList(codeChallenge.getValue()));
1136
1137                        if (codeChallengeMethod != null) {
1138                                params.put("code_challenge_method", Collections.singletonList(codeChallengeMethod.getValue()));
1139                        }
1140                }
1141                
1142                if (includeGrantedScopes)
1143                        params.put("include_granted_scopes", Collections.singletonList("true"));
1144                
1145                if (resources != null)
1146                        params.put("resource", URIUtils.toStringList(resources));
1147                
1148                if (requestObject != null) {
1149                        try {
1150                                params.put("request", Collections.singletonList(requestObject.serialize()));
1151                                
1152                        } catch (IllegalStateException e) {
1153                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1154                        }
1155                }
1156                
1157                if (requestURI != null)
1158                        params.put("request_uri", Collections.singletonList(requestURI.toString()));
1159                
1160                if (prompt != null)
1161                        params.put("prompt", Collections.singletonList(prompt.toString()));
1162
1163                return params;
1164        }
1165        
1166        
1167        /**
1168         * Returns the parameters for this authorisation request as a JSON Web
1169         * Token (JWT) claims set. Intended for creating a request object.
1170         *
1171         * @return The parameters as JWT claim set.
1172         */
1173        public JWTClaimsSet toJWTClaimsSet() {
1174                
1175                if (specifiesRequestObject()) {
1176                        throw new IllegalStateException("Cannot create nested JWT secured authorization request");
1177                }
1178                
1179                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
1180        }
1181        
1182        
1183        /**
1184         * Returns the URI query string for this authorisation request.
1185         *
1186         * <p>Note that the '?' character preceding the query string in an URI
1187         * is not included in the returned string.
1188         *
1189         * <p>Example URI query string:
1190         *
1191         * <pre>
1192         * response_type=code
1193         * &amp;client_id=s6BhdRkqt3
1194         * &amp;state=xyz
1195         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1196         * </pre>
1197         * 
1198         * @return The URI query string.
1199         */
1200        public String toQueryString() {
1201                
1202                Map<String, List<String>> params = new HashMap<>();
1203                if (getEndpointURI() != null) {
1204                        params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery()));
1205                }
1206                params.putAll(toParameters());
1207                
1208                return URLUtils.serializeParameters(params);
1209        }
1210
1211
1212        /**
1213         * Returns the complete URI representation for this authorisation
1214         * request, consisting of the {@link #getEndpointURI authorization
1215         * endpoint URI} with the {@link #toQueryString query string} appended.
1216         *
1217         * <p>Example URI:
1218         *
1219         * <pre>
1220         * https://server.example.com/authorize?
1221         * response_type=code
1222         * &amp;client_id=s6BhdRkqt3
1223         * &amp;state=xyz
1224         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1225         * </pre>
1226         *
1227         * @return The URI representation.
1228         */
1229        public URI toURI() {
1230
1231                if (getEndpointURI() == null)
1232                        throw new SerializeException("The authorization endpoint URI is not specified");
1233                
1234                StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString());
1235                sb.append('?');
1236                sb.append(toQueryString());
1237                try {
1238                        return new URI(sb.toString());
1239                } catch (URISyntaxException e) {
1240                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
1241                }
1242        }
1243        
1244        
1245        /**
1246         * Returns the matching HTTP request.
1247         *
1248         * @param method The HTTP request method which can be GET or POST. Must
1249         *               not be {@code null}.
1250         *
1251         * @return The HTTP request.
1252         */
1253        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
1254                
1255                if (getEndpointURI() == null)
1256                        throw new SerializeException("The endpoint URI is not specified");
1257                
1258                HTTPRequest httpRequest;
1259                if (method.equals(HTTPRequest.Method.GET)) {
1260                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, getEndpointURI());
1261                } else if (method.equals(HTTPRequest.Method.POST)) {
1262                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
1263                } else {
1264                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
1265                }
1266                
1267                httpRequest.setQuery(toQueryString());
1268                
1269                return httpRequest;
1270        }
1271        
1272        
1273        @Override
1274        public HTTPRequest toHTTPRequest() {
1275        
1276                return toHTTPRequest(HTTPRequest.Method.GET);
1277        }
1278
1279
1280        /**
1281         * Parses an authorisation request from the specified URI query
1282         * parameters.
1283         *
1284         * <p>Example parameters:
1285         *
1286         * <pre>
1287         * response_type = code
1288         * client_id     = s6BhdRkqt3
1289         * state         = xyz
1290         * redirect_uri  = https://client.example.com/cb
1291         * </pre>
1292         *
1293         * @param params The parameters. Must not be {@code null}.
1294         *
1295         * @return The authorisation request.
1296         *
1297         * @throws ParseException If the parameters couldn't be parsed to an
1298         *                        authorisation request.
1299         */
1300        public static AuthorizationRequest parse(final Map<String,List<String>> params)
1301                throws ParseException {
1302
1303                return parse(null, params);
1304        }
1305
1306
1307        /**
1308         * Parses an authorisation request from the specified URI and query
1309         * parameters.
1310         *
1311         * <p>Example parameters:
1312         *
1313         * <pre>
1314         * response_type = code
1315         * client_id     = s6BhdRkqt3
1316         * state         = xyz
1317         * redirect_uri  = https://client.example.com/cb
1318         * </pre>
1319         *
1320         * @param uri    The URI of the authorisation endpoint. May be
1321         *               {@code null} if the {@link #toHTTPRequest()} method
1322         *               will not be used.
1323         * @param params The parameters. Must not be {@code null}.
1324         *
1325         * @return The authorisation request.
1326         *
1327         * @throws ParseException If the parameters couldn't be parsed to an
1328         *                        authorisation request.
1329         */
1330        public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params)
1331                throws ParseException {
1332                
1333                Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, Collections.singleton("resource"));
1334                
1335                if (! repeatParams.isEmpty()) {
1336                        // Always result in non-redirecting error. Technically
1337                        // only duplicate client_id, state, redirect_uri,
1338                        // response_type, request_uri and request should
1339                        String msg = "Parameter(s) present more than once: " + repeatParams;
1340                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg));
1341                }
1342                
1343                // Parse response_mode, response_type, client_id, redirect_uri and state first,
1344                // needed if parsing results in a error response
1345                final ClientID clientID;
1346                URI redirectURI = null;
1347                State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state"));
1348                ResponseMode rm = null;
1349                ResponseType rt = null;
1350                
1351                // Optional response_mode
1352                String v = MultivaluedMapUtils.getFirstValue(params, "response_mode");
1353                if (StringUtils.isNotBlank(v)) {
1354                        rm = new ResponseMode(v);
1355                }
1356                
1357                // Mandatory client_id
1358                v = MultivaluedMapUtils.getFirstValue(params, "client_id");
1359                if (StringUtils.isBlank(v)) {
1360                        // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1361                        String msg = "Missing client_id parameter";
1362                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1363                }
1364                clientID = new ClientID(v);
1365                
1366                // Optional redirect_uri
1367                v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri");
1368                if (StringUtils.isNotBlank(v)) {
1369                        try {
1370                                redirectURI = new URI(v);
1371                        } catch (URISyntaxException e) {
1372                                // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1373                                String msg = "Invalid redirect_uri parameter: " + e.getMessage();
1374                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1375                        }
1376                }
1377                
1378                // Mandatory response_type, unless in JAR
1379                v = MultivaluedMapUtils.getFirstValue(params, "response_type");
1380                if (StringUtils.isNotBlank(v)) {
1381                        try {
1382                                rt = ResponseType.parse(v);
1383                        } catch (ParseException e) {
1384                                // Only cause
1385                                String msg = "Invalid response_type parameter";
1386                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1387                                        clientID, redirectURI, rm, state, e);
1388                        }
1389                }
1390                
1391                // Check for a JAR in request or request_uri parameters
1392                v = MultivaluedMapUtils.getFirstValue(params, "request_uri");
1393                URI requestURI = null;
1394                if (StringUtils.isNotBlank(v)) {
1395                        try {
1396                                requestURI = new URI(v);
1397                        } catch (URISyntaxException e) {
1398                                String msg = "Invalid request_uri parameter: " + e.getMessage();
1399                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1400                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1401                        }
1402                }
1403                
1404                v = MultivaluedMapUtils.getFirstValue(params, "request");
1405                
1406                JWT requestObject = null;
1407                
1408                if (StringUtils.isNotBlank(v)) {
1409                        
1410                        // request_object and request_uri must not be present at the same time
1411                        if (requestURI != null) {
1412                                String msg = "Invalid request: Found mutually exclusive request and request_uri parameters";
1413                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1414                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1415                        }
1416                        
1417                        try {
1418                                requestObject = JWTParser.parse(v);
1419                                
1420                                if (requestObject instanceof SignedJWT) {
1421                                        // Make sure the "sub" claim is not set to the client_id value
1422                                        // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8
1423                                        JWTClaimsSet requestObjectClaims = requestObject.getJWTClaimsSet();
1424                                        if (clientID.getValue().equals(requestObjectClaims.getSubject())) {
1425                                                throw new java.text.ParseException("The JWT sub (subject) claim must not equal the client_id", 0);
1426                                        }
1427                                }
1428                                
1429                        } catch (java.text.ParseException e) {
1430                                String msg = "Invalid request parameter: " + e.getMessage();
1431                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1432                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1433                        }
1434                }
1435
1436                // Response type mandatory, unless in JAR
1437                if (rt == null && requestObject == null && requestURI == null) {
1438                        String msg = "Missing response_type parameter";
1439                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1440                                clientID, redirectURI, ResponseMode.resolve(rm, null), state, null);
1441                }
1442
1443
1444                // Parse optional scope
1445                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1446
1447                Scope scope = null;
1448
1449                if (StringUtils.isNotBlank(v))
1450                        scope = Scope.parse(v);
1451
1452
1453                // Parse optional code challenge and method for PKCE
1454                CodeChallenge codeChallenge = null;
1455                CodeChallengeMethod codeChallengeMethod = null;
1456
1457                v = MultivaluedMapUtils.getFirstValue(params, "code_challenge");
1458
1459                if (StringUtils.isNotBlank(v))
1460                        codeChallenge = CodeChallenge.parse(v);
1461
1462                if (codeChallenge != null) {
1463
1464                        v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method");
1465
1466                        if (StringUtils.isNotBlank(v))
1467                                codeChallengeMethod = CodeChallengeMethod.parse(v);
1468                }
1469                
1470                List<URI> resources = null;
1471                
1472                List<String> vList = params.get("resource");
1473                
1474                if (vList != null) {
1475                        
1476                        resources = new LinkedList<>();
1477                        
1478                        for (String uriValue: vList) {
1479                                
1480                                if (uriValue == null)
1481                                        continue;
1482                                
1483                                String errMsg = "Illegal resource parameter: Must be an absolute URI and with no query or fragment: " + uriValue;
1484                                
1485                                URI resourceURI;
1486                                try {
1487                                        resourceURI = new URI(uriValue);
1488                                } catch (URISyntaxException e) {
1489                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1490                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1491                                }
1492                                
1493                                if (! ResourceUtils.isValidResourceURI(resourceURI)) {
1494                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1495                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1496                                }
1497                                
1498                                resources.add(resourceURI);
1499                        }
1500                }
1501                
1502                boolean includeGrantedScopes = false;
1503                v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes");
1504                if ("true".equals(v)) {
1505                        includeGrantedScopes = true;
1506                }
1507                
1508                Prompt prompt;
1509                try {
1510                        prompt = Prompt.parse(MultivaluedMapUtils.getFirstValue(params, "prompt"));
1511                        
1512                } catch (ParseException e) {
1513                        String msg = "Invalid prompt parameter: " + e.getMessage();
1514                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1515                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1516                }
1517                
1518                // Parse custom parameters
1519                Map<String,List<String>> customParams = null;
1520
1521                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1522
1523                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1524                                // We have a custom parameter
1525                                if (customParams == null) {
1526                                        customParams = new HashMap<>();
1527                                }
1528                                customParams.put(p.getKey(), p.getValue());
1529                        }
1530                }
1531
1532
1533                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
1534                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
1535                        requestObject, requestURI,
1536                        prompt,
1537                        customParams);
1538        }
1539
1540
1541        /**
1542         * Parses an authorisation request from the specified URI query string.
1543         *
1544         * <p>Example URI query string:
1545         *
1546         * <pre>
1547         * response_type=code
1548         * &amp;client_id=s6BhdRkqt3
1549         * &amp;state=xyz
1550         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1551         * </pre>
1552         *
1553         * @param query The URI query string. Must not be {@code null}.
1554         *
1555         * @return The authorisation request.
1556         *
1557         * @throws ParseException If the query string couldn't be parsed to an
1558         *                        authorisation request.
1559         */
1560        public static AuthorizationRequest parse(final String query)
1561                throws ParseException {
1562
1563                return parse(null, URLUtils.parseParameters(query));
1564        }
1565        
1566        
1567        /**
1568         * Parses an authorisation request from the specified URI and query
1569         * string.
1570         *
1571         * <p>Example URI query string:
1572         *
1573         * <pre>
1574         * response_type=code
1575         * &amp;client_id=s6BhdRkqt3
1576         * &amp;state=xyz
1577         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1578         * </pre>
1579         *
1580         * @param uri   The URI of the authorisation endpoint. May be 
1581         *              {@code null} if the {@link #toHTTPRequest()} method
1582         *              will not be used.
1583         * @param query The URI query string. Must not be {@code null}.
1584         *
1585         * @return The authorisation request.
1586         *
1587         * @throws ParseException If the query string couldn't be parsed to an 
1588         *                        authorisation request.
1589         */
1590        public static AuthorizationRequest parse(final URI uri, final String query)
1591                throws ParseException {
1592        
1593                return parse(uri, URLUtils.parseParameters(query));
1594        }
1595
1596
1597        /**
1598         * Parses an authorisation request from the specified URI.
1599         *
1600         * <p>Example URI:
1601         *
1602         * <pre>
1603         * https://server.example.com/authorize?
1604         * response_type=code
1605         * &amp;client_id=s6BhdRkqt3
1606         * &amp;state=xyz
1607         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1608         * </pre>
1609         *
1610         * @param uri The URI. Must not be {@code null}.
1611         *
1612         * @return The authorisation request.
1613         *
1614         * @throws ParseException If the URI couldn't be parsed to an
1615         *                        authorisation request.
1616         */
1617        public static AuthorizationRequest parse(final URI uri)
1618                throws ParseException {
1619
1620                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1621        }
1622        
1623        
1624        /**
1625         * Parses an authorisation request from the specified HTTP request.
1626         *
1627         * <p>Example HTTP request (GET):
1628         *
1629         * <pre>
1630         * https://server.example.com/authorize?
1631         * response_type=code
1632         * &amp;client_id=s6BhdRkqt3
1633         * &amp;state=xyz
1634         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1635         * </pre>
1636         *
1637         * @param httpRequest The HTTP request. Must not be {@code null}.
1638         *
1639         * @return The authorisation request.
1640         *
1641         * @throws ParseException If the HTTP request couldn't be parsed to an 
1642         *                        authorisation request.
1643         */
1644        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
1645                throws ParseException {
1646                
1647                String query = httpRequest.getQuery();
1648                
1649                if (query == null)
1650                        throw new ParseException("Missing URI query string");
1651                
1652                return parse(URIUtils.getBaseURI(httpRequest.getURI()), query);
1653        }
1654}