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         * @throws SerializeException If this authorisation request couldn't be
435         *                            serialised to an parameters map.
436         */
437        public Map<String,String> toParameters()
438                throws SerializeException {
439
440                Map <String,String> params = new LinkedHashMap<>();
441                
442                params.put("response_type", rt.toString());
443                params.put("client_id", clientID.getValue());
444
445                if (rm != null) {
446                        params.put("response_mode", rm.getValue());
447                }
448
449                if (redirectURI != null)
450                        params.put("redirect_uri", redirectURI.toString());
451
452                if (scope != null)
453                        params.put("scope", scope.toString());
454                
455                if (state != null)
456                        params.put("state", state.getValue());
457
458                return params;
459        }
460        
461        
462        /**
463         * Returns the URI query string for this authorisation request.
464         *
465         * <p>Note that the '?' character preceding the query string in an URI
466         * is not included in the returned string.
467         *
468         * <p>Example URI query string:
469         *
470         * <pre>
471         * response_type=code
472         * &amp;client_id=s6BhdRkqt3
473         * &amp;state=xyz
474         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
475         * </pre>
476         * 
477         * @return The URI query string.
478         *
479         * @throws SerializeException If this authorisation request couldn't be
480         *                            serialised to an URI query string.
481         */
482        public String toQueryString()
483                throws SerializeException {
484                
485                return URLUtils.serializeParameters(toParameters());
486        }
487
488
489        /**
490         * Returns the complete URI representation for this authorisation
491         * request, consisting of the {@link #getEndpointURI authorization
492         * endpoint URI} with the {@link #toQueryString query string} appended.
493         *
494         * <p>Example URI:
495         *
496         * <pre>
497         * https://server.example.com/authorize?
498         * response_type=code
499         * &amp;client_id=s6BhdRkqt3
500         * &amp;state=xyz
501         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
502         * </pre>
503         *
504         * @return The URI representation.
505         *
506         * @throws SerializeException If this authorisation request couldn't be
507         *                            serialised to a URI.
508         */
509        public URI toURI()
510                throws SerializeException {
511
512                if (getEndpointURI() == null)
513                        throw new SerializeException("The authorization endpoint URI is not specified");
514
515                StringBuilder sb = new StringBuilder(getEndpointURI().toString());
516                sb.append('?');
517                sb.append(toQueryString());
518                try {
519                        return new URI(sb.toString());
520                } catch (URISyntaxException e) {
521                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
522                }
523        }
524        
525        
526        /**
527         * Returns the matching HTTP request.
528         *
529         * @param method The HTTP request method which can be GET or POST. Must
530         *               not be {@code null}.
531         *
532         * @return The HTTP request.
533         *
534         * @throws SerializeException If the authorisation request message
535         *                            couldn't be serialised to an HTTP  
536         *                            request.
537         */
538        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method)
539                throws SerializeException {
540                
541                if (getEndpointURI() == null)
542                        throw new SerializeException("The endpoint URI is not specified");
543                
544                HTTPRequest httpRequest;
545
546                URL endpointURL;
547
548                try {
549                        endpointURL = getEndpointURI().toURL();
550
551                } catch (MalformedURLException e) {
552
553                        throw new SerializeException(e.getMessage(), e);
554                }
555                
556                if (method.equals(HTTPRequest.Method.GET)) {
557
558                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
559
560                } else if (method.equals(HTTPRequest.Method.POST)) {
561
562                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
563
564                } else {
565
566                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
567                }
568                
569                httpRequest.setQuery(toQueryString());
570                
571                return httpRequest;
572        }
573        
574        
575        @Override
576        public HTTPRequest toHTTPRequest()
577                throws SerializeException {
578        
579                return toHTTPRequest(HTTPRequest.Method.GET);
580        }
581
582
583        /**
584         * Parses an authorisation request from the specified parameters.
585         *
586         * <p>Example parameters:
587         *
588         * <pre>
589         * response_type = code
590         * client_id     = s6BhdRkqt3
591         * state         = xyz
592         * redirect_uri  = https://client.example.com/cb
593         * </pre>
594         *
595         * @param params The parameters. Must not be {@code null}.
596         *
597         * @return The authorisation request.
598         *
599         * @throws ParseException If the parameters couldn't be parsed to an
600         *                        authorisation request.
601         */
602        public static AuthorizationRequest parse(final Map<String,String> params)
603                throws ParseException {
604
605                return parse(null, params);
606        }
607
608
609        /**
610         * Parses an authorisation request from the specified parameters.
611         *
612         * <p>Example parameters:
613         *
614         * <pre>
615         * response_type = code
616         * client_id     = s6BhdRkqt3
617         * state         = xyz
618         * redirect_uri  = https://client.example.com/cb
619         * </pre>
620         *
621         * @param uri    The URI of the authorisation endpoint. May be
622         *               {@code null} if the {@link #toHTTPRequest()} method
623         *               will not be used.
624         * @param params The parameters. Must not be {@code null}.
625         *
626         * @return The authorisation request.
627         *
628         * @throws ParseException If the parameters couldn't be parsed to an
629         *                        authorisation request.
630         */
631        public static AuthorizationRequest parse(final URI uri, final Map<String,String> params)
632                throws ParseException {
633
634                // Parse mandatory client ID first
635                String v = params.get("client_id");
636
637                if (StringUtils.isBlank(v)) {
638                        String msg = "Missing \"client_id\" parameter";
639                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
640                }
641
642                ClientID clientID = new ClientID(v);
643
644
645                // Parse optional redirection URI second
646                v = params.get("redirect_uri");
647
648                URI redirectURI = null;
649
650                if (StringUtils.isNotBlank(v)) {
651
652                        try {
653                                redirectURI = new URI(v);
654
655                        } catch (URISyntaxException e) {
656                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
657                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
658                                                         clientID, null, null, null, e);
659                        }
660                }
661
662
663                // Parse optional state third
664                State state = State.parse(params.get("state"));
665
666
667                // Parse mandatory response type
668                v = params.get("response_type");
669
670                ResponseType rt;
671
672                try {
673                        rt = ResponseType.parse(v);
674
675                } catch (ParseException e) {
676                        // Only cause
677                        String msg = "Missing \"response_type\" parameter";
678                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
679                                                 clientID, redirectURI, null, state, e);
680                }
681
682
683                // Parse the optional response mode
684                v = params.get("response_mode");
685
686                ResponseMode rm = null;
687
688                if (StringUtils.isNotBlank(v)) {
689                        rm = new ResponseMode(v);
690                }
691
692
693                // Parse optional scope
694                v = params.get("scope");
695
696                Scope scope = null;
697
698                if (StringUtils.isNotBlank(v))
699                        scope = Scope.parse(v);
700
701
702                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state);
703        }
704
705
706        /**
707         * Parses an authorisation request from the specified URI query string.
708         *
709         * <p>Example URI query string:
710         *
711         * <pre>
712         * response_type=code
713         * &amp;client_id=s6BhdRkqt3
714         * &amp;state=xyz
715         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
716         * </pre>
717         *
718         * @param query The URI query string. Must not be {@code null}.
719         *
720         * @return The authorisation request.
721         *
722         * @throws ParseException If the query string couldn't be parsed to an
723         *                        authorisation request.
724         */
725        public static AuthorizationRequest parse(final String query)
726                throws ParseException {
727
728                return parse(null, URLUtils.parseParameters(query));
729        }
730        
731        
732        /**
733         * Parses an authorisation request from the specified URI query string.
734         *
735         * <p>Example URI query string:
736         *
737         * <pre>
738         * response_type=code
739         * &amp;client_id=s6BhdRkqt3
740         * &amp;state=xyz
741         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
742         * </pre>
743         *
744         * @param uri   The URI of the authorisation endpoint. May be 
745         *              {@code null} if the {@link #toHTTPRequest()} method
746         *              will not be used.
747         * @param query The URI query string. Must not be {@code null}.
748         *
749         * @return The authorisation request.
750         *
751         * @throws ParseException If the query string couldn't be parsed to an 
752         *                        authorisation request.
753         */
754        public static AuthorizationRequest parse(final URI uri, final String query)
755                throws ParseException {
756        
757                return parse(uri, URLUtils.parseParameters(query));
758        }
759
760
761        /**
762         * Parses an authorisation request from the specified URI.
763         *
764         * <p>Example URI:
765         *
766         * <pre>
767         * https://server.example.com/authorize?
768         * response_type=code
769         * &amp;client_id=s6BhdRkqt3
770         * &amp;state=xyz
771         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
772         * </pre>
773         *
774         * @param uri The URI. Must not be {@code null}.
775         *
776         * @return The authorisation request.
777         *
778         * @throws ParseException If the URI couldn't be parsed to an
779         *                        authorisation request.
780         */
781        public static AuthorizationRequest parse(final URI uri)
782                throws ParseException {
783
784                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getQuery()));
785        }
786        
787        
788        /**
789         * Parses an authorisation request from the specified HTTP request.
790         *
791         * <p>Example HTTP request (GET):
792         *
793         * <pre>
794         * https://server.example.com/authorize?
795         * response_type=code
796         * &amp;client_id=s6BhdRkqt3
797         * &amp;state=xyz
798         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
799         * </pre>
800         *
801         * @param httpRequest The HTTP request. Must not be {@code null}.
802         *
803         * @return The authorisation request.
804         *
805         * @throws ParseException If the HTTP request couldn't be parsed to an 
806         *                        authorisation request.
807         */
808        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
809                throws ParseException {
810                
811                String query = httpRequest.getQuery();
812                
813                if (query == null)
814                        throw new ParseException("Missing URI query string");
815
816                try {
817                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
818
819                } catch (URISyntaxException e) {
820
821                        throw new ParseException(e.getMessage(), e);
822                }
823        }
824}