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