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