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