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