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