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