001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.MalformedURLException;
005import java.net.URI;
006import java.net.URISyntaxException;
007import java.net.URL;
008import java.util.LinkedHashMap;
009import java.util.Map;
010
011import com.nimbusds.oauth2.sdk.http.HTTPRequest;
012import com.nimbusds.oauth2.sdk.id.ClientID;
013import com.nimbusds.oauth2.sdk.id.State;
014import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
015import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
016import com.nimbusds.oauth2.sdk.util.URIUtils;
017import com.nimbusds.oauth2.sdk.util.URLUtils;
018import net.jcip.annotations.Immutable;
019import org.apache.commons.lang3.StringUtils;
020
021
022/**
023 * Authorisation request. Used to authenticate an end-user and request the
024 * end-user's consent to grant the client access to a protected resource.
025 *
026 * <p>Extending classes may define additional request parameters as well as 
027 * enforce tighter requirements on the base parameters.
028 *
029 * <p>Example HTTP request:
030 *
031 * <pre>
032 * https://server.example.com/authorize?
033 * response_type=code
034 * &amp;client_id=s6BhdRkqt3
035 * &amp;state=xyz
036 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
037 * </pre>
038 *
039 * <p>Related specifications:
040 *
041 * <ul>
042 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
043 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
044 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
045 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
046 * </ul>
047 */
048@Immutable
049public class AuthorizationRequest extends AbstractRequest {
050
051
052        /**
053         * The response type (required).
054         */
055        private final ResponseType rt;
056
057
058        /**
059         * The client identifier (required).
060         */
061        private final ClientID clientID;
062
063
064        /**
065         * The redirection URI where the response will be sent (optional). 
066         */
067        private final URI redirectURI;
068        
069        
070        /**
071         * The scope (optional).
072         */
073        private final Scope scope;
074        
075        
076        /**
077         * The opaque value to maintain state between the request and the 
078         * callback (recommended).
079         */
080        private final State state;
081
082
083        /**
084         * The response mode (optional).
085         */
086        private final ResponseMode rm;
087
088
089        /**
090         * The authorisation code challenge for PKCE (optional).
091         */
092        private final CodeChallenge codeChallenge;
093
094
095        /**
096         * The authorisation code challenge method for PKCE (optional).
097         */
098        private final CodeChallengeMethod codeChallengeMethod;
099
100
101        /**
102         * Builder for constructing authorisation requests.
103         */
104        public static class Builder {
105
106
107                /**
108                 * The endpoint URI (optional).
109                 */
110                private URI uri;
111
112
113                /**
114                 * The response type (required).
115                 */
116                private final ResponseType rt;
117
118
119                /**
120                 * The client identifier (required).
121                 */
122                private final ClientID clientID;
123
124
125                /**
126                 * The redirection URI where the response will be sent
127                 * (optional).
128                 */
129                private URI redirectURI;
130
131
132                /**
133                 * The scope (optional).
134                 */
135                private Scope scope;
136
137
138                /**
139                 * The opaque value to maintain state between the request and
140                 * the callback (recommended).
141                 */
142                private State state;
143
144
145                /**
146                 * The response mode (optional).
147                 */
148                private ResponseMode rm;
149
150
151                /**
152                 * The authorisation code challenge for PKCE (optional).
153                 */
154                private CodeChallenge codeChallenge;
155
156
157                /**
158                 * The authorisation code challenge method for PKCE (optional).
159                 */
160                private CodeChallengeMethod codeChallengeMethod;
161
162
163                /**
164                 * Creates a new authorisation request builder.
165                 *
166                 * @param rt       The response type. Corresponds to the
167                 *                 {@code response_type} parameter. Must not be
168                 *                 {@code null}.
169                 * @param clientID The client identifier. Corresponds to the
170                 *                 {@code client_id} parameter. Must not be
171                 *                 {@code null}.
172                 */
173                public Builder(final ResponseType rt, final ClientID clientID) {
174
175                        if (rt == null)
176                                throw new IllegalArgumentException("The response type must not be null");
177
178                        this.rt = rt;
179
180
181                        if (clientID == null)
182                                throw new IllegalArgumentException("The client ID must not be null");
183
184                        this.clientID = clientID;
185                }
186
187
188                /**
189                 * Sets the redirection URI. Corresponds to the optional
190                 * {@code redirection_uri} parameter.
191                 *
192                 * @param redirectURI The redirection URI, {@code null} if not
193                 *                    specified.
194                 *
195                 * @return This builder.
196                 */
197                public Builder redirectionURI(final URI redirectURI) {
198
199                        this.redirectURI = redirectURI;
200                        return this;
201                }
202
203
204                /**
205                 * Sets the scope. Corresponds to the optional {@code scope}
206                 * parameter.
207                 *
208                 * @param scope The scope, {@code null} if not specified.
209                 *
210                 * @return This builder.
211                 */
212                public Builder scope(final Scope scope) {
213
214                        this.scope = scope;
215                        return this;
216                }
217
218
219                /**
220                 * Sets the state. Corresponds to the recommended {@code state}
221                 * parameter.
222                 *
223                 * @param state The state, {@code null} if not specified.
224                 *
225                 * @return This builder.
226                 */
227                public Builder state(final State state) {
228
229                        this.state = state;
230                        return this;
231                }
232
233
234                /**
235                 * Sets the response mode. Corresponds to the optional
236                 * {@code response_mode} parameter. Use of this parameter is
237                 * not recommended unless a non-default response mode is
238                 * requested (e.g. form_post).
239                 *
240                 * @param rm The response mode, {@code null} if not specified.
241                 *
242                 * @return This builder.
243                 */
244                public Builder responseMode(final ResponseMode rm) {
245
246                        this.rm = rm;
247                        return this;
248                }
249
250
251                /**
252                 * Sets the code challenge for Proof Key for Code Exchange
253                 * (PKCE) by public OAuth clients.
254                 *
255                 * @param codeChallenge       The code challenge, {@code null}
256                 *                            if not specified.
257                 * @param codeChallengeMethod The code challenge method,
258                 *                            {@code null} if not specified.
259                 *
260                 * @return This builder.
261                 */
262                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
263
264                        this.codeChallenge = codeChallenge;
265                        this.codeChallengeMethod = codeChallengeMethod;
266                        return this;
267                }
268
269
270                /**
271                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
272                 * request is intended.
273                 *
274                 * @param uri The endpoint URI, {@code null} if not specified.
275                 *
276                 * @return This builder.
277                 */
278                public Builder endpointURI(final URI uri) {
279
280                        this.uri = uri;
281                        return this;
282                }
283
284
285                /**
286                 * Builds a new authorisation request.
287                 *
288                 * @return The authorisation request.
289                 */
290                public AuthorizationRequest build() {
291
292                        return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod);
293                }
294        }
295
296
297        /**
298         * Creates a new minimal authorisation request.
299         *
300         * @param uri      The URI of the authorisation endpoint. May be
301         *                 {@code null} if the {@link #toHTTPRequest} method
302         *                 will not be used.
303         * @param rt       The response type. Corresponds to the
304         *                 {@code response_type} parameter. Must not be
305         *                 {@code null}.
306         * @param clientID The client identifier. Corresponds to the
307         *                 {@code client_id} parameter. Must not be
308         *                 {@code null}.
309         */
310        public AuthorizationRequest(final URI uri,
311                                    final ResponseType rt,
312                                    final ClientID clientID) {
313
314                this(uri, rt, null, clientID, null, null, null, null, null);
315        }
316
317
318        /**
319         * Creates a new authorisation request.
320         *
321         * @param uri                 The URI of the authorisation endpoint.
322         *                            May be {@code null} if the
323         *                            {@link #toHTTPRequest} method will not be
324         *                            used.
325         * @param rt                  The response type. Corresponds to the
326         *                            {@code response_type} parameter. Must not
327         *                            be {@code null}.
328         * @param rm                  The response mode. Corresponds to the
329         *                            optional {@code response_mode} parameter.
330         *                            Use of this parameter is not recommended
331         *                            unless a non-default response mode is
332         *                            requested (e.g. form_post).
333         * @param clientID            The client identifier. Corresponds to the
334         *                            {@code client_id} parameter. Must not be
335         *                            {@code null}.
336         * @param redirectURI         The redirection URI. Corresponds to the
337         *                            optional {@code redirect_uri} parameter.
338         *                            {@code null} if not specified.
339         * @param scope               The request scope. Corresponds to the
340         *                            optional {@code scope} parameter.
341         *                            {@code null} if not specified.
342         * @param state               The state. Corresponds to the recommended
343         *                            {@code state} parameter. {@code null} if
344         *                            not specified.
345         * @param codeChallenge       The code challenge for PKCE, {@code null}
346         *                            if not specified.
347         * @param codeChallengeMethod The code challenge method for PKCE,
348         *                            {@code null} if not specified.
349         */
350        public AuthorizationRequest(final URI uri,
351                                    final ResponseType rt,
352                                    final ResponseMode rm,
353                                    final ClientID clientID,
354                                    final URI redirectURI,
355                                    final Scope scope,
356                                    final State state,
357                                    final CodeChallenge codeChallenge,
358                                    final CodeChallengeMethod codeChallengeMethod) {
359
360                super(uri);
361
362                if (rt == null)
363                        throw new IllegalArgumentException("The response type must not be null");
364
365                this.rt = rt;
366
367                this.rm = rm;
368
369
370                if (clientID == null)
371                        throw new IllegalArgumentException("The client ID must not be null");
372
373                this.clientID = clientID;
374
375
376                this.redirectURI = redirectURI;
377                this.scope = scope;
378                this.state = state;
379
380                this.codeChallenge = codeChallenge;
381                this.codeChallengeMethod = codeChallengeMethod;
382        }
383        
384        
385        /**
386         * Gets the response type. Corresponds to the {@code response_type}
387         * parameter.
388         *
389         * @return The response type.
390         */
391        public ResponseType getResponseType() {
392        
393                return rt;
394        }
395
396
397        /**
398         * Gets the optional response mode. Corresponds to the optional
399         * {@code response_mode} parameter.
400         *
401         * @return The response mode, {@code null} if not specified.
402         */
403        public ResponseMode getResponseMode() {
404
405                return rm;
406        }
407
408
409        /**
410         * Returns the implied response mode, determined by the optional
411         * {@code response_mode} parameter, and if that isn't specified, by
412         * the {@code response_type}.
413         *
414         * @return The implied response mode.
415         */
416        public ResponseMode impliedResponseMode() {
417
418                if (rm != null) {
419                        return rm;
420                } else if (rt.impliesImplicitFlow()) {
421                        return ResponseMode.FRAGMENT;
422                } else {
423                        return ResponseMode.QUERY;
424                }
425        }
426
427
428        /**
429         * Gets the client identifier. Corresponds to the {@code client_id} 
430         * parameter.
431         *
432         * @return The client identifier.
433         */
434        public ClientID getClientID() {
435        
436                return clientID;
437        }
438
439
440        /**
441         * Gets the redirection URI. Corresponds to the optional 
442         * {@code redirection_uri} parameter.
443         *
444         * @return The redirection URI, {@code null} if not specified.
445         */
446        public URI getRedirectionURI() {
447        
448                return redirectURI;
449        }
450        
451        
452        /**
453         * Gets the scope. Corresponds to the optional {@code scope} parameter.
454         *
455         * @return The scope, {@code null} if not specified.
456         */
457        public Scope getScope() {
458        
459                return scope;
460        }
461        
462        
463        /**
464         * Gets the state. Corresponds to the recommended {@code state} 
465         * parameter.
466         *
467         * @return The state, {@code null} if not specified.
468         */
469        public State getState() {
470        
471                return state;
472        }
473
474
475        /**
476         * Returns the code challenge for PKCE.
477         *
478         * @return The code challenge, {@code null} if not specified.
479         */
480        public CodeChallenge getCodeChallenge() {
481
482                return codeChallenge;
483        }
484
485
486        /**
487         * Returns the code challenge method for PKCE.
488         *
489         * @return The code challenge method, {@code null} if not specified.
490         */
491        public CodeChallengeMethod getCodeChallengeMethod() {
492
493                return codeChallengeMethod;
494        }
495
496
497        /**
498         * Returns the parameters for this authorisation request.
499         *
500         * <p>Example parameters:
501         *
502         * <pre>
503         * response_type = code
504         * client_id     = s6BhdRkqt3
505         * state         = xyz
506         * redirect_uri  = https://client.example.com/cb
507         * </pre>
508         * 
509         * @return The parameters.
510         */
511        public Map<String,String> toParameters() {
512
513                Map <String,String> params = new LinkedHashMap<>();
514                
515                params.put("response_type", rt.toString());
516                params.put("client_id", clientID.getValue());
517
518                if (rm != null) {
519                        params.put("response_mode", rm.getValue());
520                }
521
522                if (redirectURI != null)
523                        params.put("redirect_uri", redirectURI.toString());
524
525                if (scope != null)
526                        params.put("scope", scope.toString());
527                
528                if (state != null)
529                        params.put("state", state.getValue());
530
531                if (codeChallenge != null) {
532                        params.put("code_challenge", codeChallenge.getValue());
533
534                        if (codeChallengeMethod != null) {
535                                params.put("code_challenge_method", codeChallengeMethod.getValue());
536                        }
537                }
538
539                return params;
540        }
541        
542        
543        /**
544         * Returns the URI query string for this authorisation request.
545         *
546         * <p>Note that the '?' character preceding the query string in an URI
547         * is not included in the returned string.
548         *
549         * <p>Example URI query string:
550         *
551         * <pre>
552         * response_type=code
553         * &amp;client_id=s6BhdRkqt3
554         * &amp;state=xyz
555         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
556         * </pre>
557         * 
558         * @return The URI query string.
559         */
560        public String toQueryString() {
561                
562                return URLUtils.serializeParameters(toParameters());
563        }
564
565
566        /**
567         * Returns the complete URI representation for this authorisation
568         * request, consisting of the {@link #getEndpointURI authorization
569         * endpoint URI} with the {@link #toQueryString query string} appended.
570         *
571         * <p>Example URI:
572         *
573         * <pre>
574         * https://server.example.com/authorize?
575         * response_type=code
576         * &amp;client_id=s6BhdRkqt3
577         * &amp;state=xyz
578         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
579         * </pre>
580         *
581         * @return The URI representation.
582         */
583        public URI toURI() {
584
585                if (getEndpointURI() == null)
586                        throw new SerializeException("The authorization endpoint URI is not specified");
587
588                StringBuilder sb = new StringBuilder(getEndpointURI().toString());
589                sb.append('?');
590                sb.append(toQueryString());
591                try {
592                        return new URI(sb.toString());
593                } catch (URISyntaxException e) {
594                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
595                }
596        }
597        
598        
599        /**
600         * Returns the matching HTTP request.
601         *
602         * @param method The HTTP request method which can be GET or POST. Must
603         *               not be {@code null}.
604         *
605         * @return The HTTP request.
606         */
607        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
608                
609                if (getEndpointURI() == null)
610                        throw new SerializeException("The endpoint URI is not specified");
611                
612                HTTPRequest httpRequest;
613
614                URL endpointURL;
615
616                try {
617                        endpointURL = getEndpointURI().toURL();
618
619                } catch (MalformedURLException e) {
620
621                        throw new SerializeException(e.getMessage(), e);
622                }
623                
624                if (method.equals(HTTPRequest.Method.GET)) {
625
626                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
627
628                } else if (method.equals(HTTPRequest.Method.POST)) {
629
630                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
631
632                } else {
633
634                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
635                }
636                
637                httpRequest.setQuery(toQueryString());
638                
639                return httpRequest;
640        }
641        
642        
643        @Override
644        public HTTPRequest toHTTPRequest() {
645        
646                return toHTTPRequest(HTTPRequest.Method.GET);
647        }
648
649
650        /**
651         * Parses an authorisation request from the specified parameters.
652         *
653         * <p>Example parameters:
654         *
655         * <pre>
656         * response_type = code
657         * client_id     = s6BhdRkqt3
658         * state         = xyz
659         * redirect_uri  = https://client.example.com/cb
660         * </pre>
661         *
662         * @param params The parameters. Must not be {@code null}.
663         *
664         * @return The authorisation request.
665         *
666         * @throws ParseException If the parameters couldn't be parsed to an
667         *                        authorisation request.
668         */
669        public static AuthorizationRequest parse(final Map<String,String> params)
670                throws ParseException {
671
672                return parse(null, params);
673        }
674
675
676        /**
677         * Parses an authorisation request from the specified parameters.
678         *
679         * <p>Example parameters:
680         *
681         * <pre>
682         * response_type = code
683         * client_id     = s6BhdRkqt3
684         * state         = xyz
685         * redirect_uri  = https://client.example.com/cb
686         * </pre>
687         *
688         * @param uri    The URI of the authorisation endpoint. May be
689         *               {@code null} if the {@link #toHTTPRequest()} method
690         *               will not be used.
691         * @param params The parameters. Must not be {@code null}.
692         *
693         * @return The authorisation request.
694         *
695         * @throws ParseException If the parameters couldn't be parsed to an
696         *                        authorisation request.
697         */
698        public static AuthorizationRequest parse(final URI uri, final Map<String,String> params)
699                throws ParseException {
700
701                // Parse mandatory client ID first
702                String v = params.get("client_id");
703
704                if (StringUtils.isBlank(v)) {
705                        String msg = "Missing \"client_id\" parameter";
706                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
707                }
708
709                ClientID clientID = new ClientID(v);
710
711
712                // Parse optional redirection URI second
713                v = params.get("redirect_uri");
714
715                URI redirectURI = null;
716
717                if (StringUtils.isNotBlank(v)) {
718
719                        try {
720                                redirectURI = new URI(v);
721
722                        } catch (URISyntaxException e) {
723                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
724                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
725                                                         clientID, null, null, null, e);
726                        }
727                }
728
729
730                // Parse optional state third
731                State state = State.parse(params.get("state"));
732
733
734                // Parse mandatory response type
735                v = params.get("response_type");
736
737                ResponseType rt;
738
739                try {
740                        rt = ResponseType.parse(v);
741
742                } catch (ParseException e) {
743                        // Only cause
744                        String msg = "Missing \"response_type\" parameter";
745                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
746                                                 clientID, redirectURI, null, state, e);
747                }
748
749
750                // Parse the optional response mode
751                v = params.get("response_mode");
752
753                ResponseMode rm = null;
754
755                if (StringUtils.isNotBlank(v)) {
756                        rm = new ResponseMode(v);
757                }
758
759
760                // Parse optional scope
761                v = params.get("scope");
762
763                Scope scope = null;
764
765                if (StringUtils.isNotBlank(v))
766                        scope = Scope.parse(v);
767
768
769                // Parse optional code challenge and method for PKCE
770                CodeChallenge codeChallenge = null;
771                CodeChallengeMethod codeChallengeMethod = null;
772
773                v = params.get("code_challenge");
774
775                if (StringUtils.isNotBlank(v))
776                        codeChallenge = new CodeChallenge(v);
777
778                if (codeChallenge != null) {
779
780                        v = params.get("code_challenge_method");
781
782                        if (StringUtils.isNotBlank(v))
783                                codeChallengeMethod = CodeChallengeMethod.parse(v);
784                }
785
786
787                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod);
788        }
789
790
791        /**
792         * Parses an authorisation request from the specified URI query string.
793         *
794         * <p>Example URI query string:
795         *
796         * <pre>
797         * response_type=code
798         * &amp;client_id=s6BhdRkqt3
799         * &amp;state=xyz
800         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
801         * </pre>
802         *
803         * @param query The URI query string. Must not be {@code null}.
804         *
805         * @return The authorisation request.
806         *
807         * @throws ParseException If the query string couldn't be parsed to an
808         *                        authorisation request.
809         */
810        public static AuthorizationRequest parse(final String query)
811                throws ParseException {
812
813                return parse(null, URLUtils.parseParameters(query));
814        }
815        
816        
817        /**
818         * Parses an authorisation request from the specified URI query string.
819         *
820         * <p>Example URI query string:
821         *
822         * <pre>
823         * response_type=code
824         * &amp;client_id=s6BhdRkqt3
825         * &amp;state=xyz
826         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
827         * </pre>
828         *
829         * @param uri   The URI of the authorisation endpoint. May be 
830         *              {@code null} if the {@link #toHTTPRequest()} method
831         *              will not be used.
832         * @param query The URI query string. Must not be {@code null}.
833         *
834         * @return The authorisation request.
835         *
836         * @throws ParseException If the query string couldn't be parsed to an 
837         *                        authorisation request.
838         */
839        public static AuthorizationRequest parse(final URI uri, final String query)
840                throws ParseException {
841        
842                return parse(uri, URLUtils.parseParameters(query));
843        }
844
845
846        /**
847         * Parses an authorisation request from the specified URI.
848         *
849         * <p>Example URI:
850         *
851         * <pre>
852         * https://server.example.com/authorize?
853         * response_type=code
854         * &amp;client_id=s6BhdRkqt3
855         * &amp;state=xyz
856         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
857         * </pre>
858         *
859         * @param uri The URI. Must not be {@code null}.
860         *
861         * @return The authorisation request.
862         *
863         * @throws ParseException If the URI couldn't be parsed to an
864         *                        authorisation request.
865         */
866        public static AuthorizationRequest parse(final URI uri)
867                throws ParseException {
868
869                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
870        }
871        
872        
873        /**
874         * Parses an authorisation request from the specified HTTP request.
875         *
876         * <p>Example HTTP request (GET):
877         *
878         * <pre>
879         * https://server.example.com/authorize?
880         * response_type=code
881         * &amp;client_id=s6BhdRkqt3
882         * &amp;state=xyz
883         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
884         * </pre>
885         *
886         * @param httpRequest The HTTP request. Must not be {@code null}.
887         *
888         * @return The authorisation request.
889         *
890         * @throws ParseException If the HTTP request couldn't be parsed to an 
891         *                        authorisation request.
892         */
893        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
894                throws ParseException {
895                
896                String query = httpRequest.getQuery();
897                
898                if (query == null)
899                        throw new ParseException("Missing URI query string");
900
901                try {
902                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
903
904                } catch (URISyntaxException e) {
905
906                        throw new ParseException(e.getMessage(), e);
907                }
908        }
909}