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 * This class is immutable.
023 *
024 * <p>Extending classes may define additional request parameters as well as 
025 * enforce tighter requirements on the base parameters.
026 *
027 * <p>Example HTTP request:
028 *
029 * <pre>
030 * https://server.example.com/authorize?
031 * response_type=code
032 * &amp;client_id=s6BhdRkqt3
033 * &amp;state=xyz
034 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
035 * </pre>
036 *
037 * <p>Related specifications:
038 *
039 * <ul>
040 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
041 * </ul>
042 *
043 * @author Vladimir Dzhuvinov
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 URL 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         * Creates a new minimal authorisation request.
082         *
083         * @param uri         The URI of the authorisation endpoint. May be 
084         *                    {@code null} if the {@link #toHTTPRequest()}
085         *                    method will not be used.
086         * @param rt          The response type. Corresponds to the 
087         *                    {@code response_type} parameter. Must not be
088         *                    {@code null}.
089         * @param clientID    The client identifier. Corresponds to the
090         *                    {@code client_id} parameter. Must not be 
091         *                    {@code null}.
092         */
093        public AuthorizationRequest(final URL uri,
094                                    final ResponseType rt,
095                                    final ClientID clientID) {
096
097                this(uri, rt, clientID, null, null, null);
098        }
099        
100        
101        /**
102         * Creates a new authorisation request.
103         *
104         *  @param uri        The URI of the authorisation endpoint. May be 
105         *                    {@code null} if the {@link #toHTTPRequest()}
106         *                    method will not be used.
107         * @param rt          The response type. Corresponds to the 
108         *                    {@code response_type} parameter. Must not be
109         *                    {@code null}.
110         * @param clientID    The client identifier. Corresponds to the
111         *                    {@code client_id} parameter. Must not be 
112         *                    {@code null}.
113         * @param redirectURI The redirection URI. Corresponds to the optional
114         *                    {@code redirect_uri} parameter. {@code null} if
115         *                    not specified.
116         * @param scope       The request scope. Corresponds to the optional
117         *                    {@code scope} parameter. {@code null} if not
118         *                    specified.
119         * @param state       The state. Corresponds to the recommended 
120         *                    {@code state} parameter. {@code null} if not 
121         *                    specified.
122         */
123        public AuthorizationRequest(final URL uri,
124                                    final ResponseType rt,
125                                    final ClientID clientID,
126                                    final URL redirectURI,
127                                    final Scope scope,
128                                    final State state) {
129
130                super(uri);
131                
132                if (rt == null)
133                        throw new IllegalArgumentException("The response type must not be null");
134                
135                this.rt = rt;
136
137
138                if (clientID == null)
139                        throw new IllegalArgumentException("The client ID must not be null");
140                        
141                this.clientID = clientID;
142                
143                
144                this.redirectURI = redirectURI;
145                this.scope = scope;
146                this.state = state;
147        }
148        
149        
150        /**
151         * Gets the response type. Corresponds to the {@code response_type}
152         * parameter.
153         *
154         * @return The response type.
155         */
156        public ResponseType getResponseType() {
157        
158                return rt;
159        }
160
161
162        /**
163         * Gets the client identifier. Corresponds to the {@code client_id} 
164         * parameter.
165         *
166         * @return The client identifier.
167         */
168        public ClientID getClientID() {
169        
170                return clientID;
171        }
172
173
174        /**
175         * Gets the redirection URI. Corresponds to the optional 
176         * {@code redirection_uri} parameter.
177         *
178         * @return The redirection URI, {@code null} if not specified.
179         */
180        public URL getRedirectionURI() {
181        
182                return redirectURI;
183        }
184        
185        
186        /**
187         * Gets the scope. Corresponds to the optional {@code scope} parameter.
188         *
189         * @return The scope, {@code null} if not specified.
190         */
191        public Scope getScope() {
192        
193                return scope;
194        }
195        
196        
197        /**
198         * Gets the state. Corresponds to the recommended {@code state} 
199         * parameter.
200         *
201         * @return The state, {@code null} if not specified.
202         */
203        public State getState() {
204        
205                return state;
206        }
207
208
209        /**
210         * Returns the parameters for this authorisation request.
211         *
212         * <p>Example parameters:
213         *
214         * <pre>
215         * response_type = code
216         * client_id     = s6BhdRkqt3
217         * state         = xyz
218         * redirect_uri  = https://client.example.com/cb
219         * </pre>
220         * 
221         * @return The parameters.
222         *
223         * @throws SerializeException If this authorisation request couldn't be
224         *                            serialised to an parameters map.
225         */
226        public Map<String,String> toParameters()
227                throws SerializeException {
228
229                Map <String,String> params = new LinkedHashMap<String,String>();
230                
231                params.put("response_type", rt.toString());
232                params.put("client_id", clientID.getValue());
233
234                if (redirectURI != null)
235                        params.put("redirect_uri", redirectURI.toString());
236
237                if (scope != null)
238                        params.put("scope", scope.toString());
239                
240                if (state != null)
241                        params.put("state", state.getValue());
242
243                return params;
244        }
245        
246        
247        /**
248         * Returns the URL query string for this authorisation request.
249         *
250         * <p>Note that the '?' character preceding the query string in an URL
251         * is not included in the returned string.
252         *
253         * <p>Example URL query string:
254         *
255         * <pre>
256         * response_type=code
257         * &amp;client_id=s6BhdRkqt3
258         * &amp;state=xyz
259         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
260         * </pre>
261         * 
262         * @return The URL query string.
263         *
264         * @throws SerializeException If this authorisation request couldn't be
265         *                            serialised to an URL query string.
266         */
267        public String toQueryString()
268                throws SerializeException {
269                
270                return URLUtils.serializeParameters(toParameters());
271        }
272        
273        
274        /**
275         * Returns the matching HTTP request.
276         *
277         * @param method The HTTP request method which can be GET or POST. Must
278         *               not be {@code null}.
279         *
280         * @return The HTTP request.
281         *
282         * @throws SerializeException If the authorisation request message
283         *                            couldn't be serialised to an HTTP  
284         *                            request.
285         */
286        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method)
287                throws SerializeException {
288                
289                if (getURI() == null)
290                        throw new SerializeException("The endpoint URI is not specified");
291                
292                HTTPRequest httpRequest;
293                
294                if (method.equals(HTTPRequest.Method.GET)) {
295
296                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, getURI());
297
298                } else if (method.equals(HTTPRequest.Method.POST)) {
299
300                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getURI());
301
302                } else {
303
304                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
305                }
306                
307                httpRequest.setQuery(toQueryString());
308                
309                return httpRequest;
310        }
311        
312        
313        @Override
314        public HTTPRequest toHTTPRequest()
315                throws SerializeException {
316        
317                return toHTTPRequest(HTTPRequest.Method.GET);
318        }
319
320
321        /**
322         * Parses an authorisation request from the specified parameters.
323         *
324         * <p>Example parameters:
325         *
326         * <pre>
327         * response_type = code
328         * client_id     = s6BhdRkqt3
329         * state         = xyz
330         * redirect_uri  = https://client.example.com/cb
331         * </pre>
332         *
333         * @param uri    The URI of the authorisation endpoint. May be 
334         *               {@code null} if the {@link #toHTTPRequest()} method 
335         *               will not be used.
336         * @param params The parameters. Must not be {@code null}.
337         *
338         * @return The authorisation request.
339         *
340         * @throws ParseException If the parameters couldn't be parsed to an
341         *                        authorisation request.
342         */
343        public static AuthorizationRequest parse(final URL uri, final Map<String,String> params)
344                throws ParseException {
345
346                // Parse mandatory client ID first
347                String v = params.get("client_id");
348                
349                if (StringUtils.isBlank(v))
350                        throw new ParseException("Missing \"client_id\" parameter", 
351                                                 OAuth2Error.INVALID_REQUEST);
352
353                ClientID clientID = new ClientID(v);
354
355
356                // Parse optional redirect URI second
357                v = params.get("redirect_uri");
358
359                URL redirectURI = null;
360
361                if (StringUtils.isNotBlank(v)) {
362                        
363                        try {
364                                redirectURI = new URL(v);
365                                
366                        } catch (MalformedURLException e) {
367                        
368                                throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), 
369                                                         OAuth2Error.INVALID_REQUEST, e);
370                        }
371                }
372
373
374                // Parse optional state third
375                State state = State.parse(params.get("state"));
376                
377
378                // Parse mandatory response type
379                v = params.get("response_type");
380                
381                ResponseType rt;
382                
383                try {
384                        rt = ResponseType.parse(v);
385                
386                } catch (ParseException e) {
387                        
388                        throw new ParseException(e.getMessage(), 
389                                                 OAuth2Error.UNSUPPORTED_RESPONSE_TYPE, 
390                                                 redirectURI, state, e);
391                }
392                        
393                
394                // Parse optional scope
395                v = params.get("scope");
396
397                Scope scope = null;
398                
399                if (StringUtils.isNotBlank(v))
400                        scope = Scope.parse(v);
401
402
403                return new AuthorizationRequest(uri, rt, clientID, redirectURI, scope, state);
404
405        }
406        
407        
408        /**
409         * Parses an authorisation request from the specified URL query string.
410         *
411         * <p>Example URL query string:
412         *
413         * <pre>
414         * response_type=code
415         * &amp;client_id=s6BhdRkqt3
416         * &amp;state=xyz
417         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
418         * </pre>
419         *
420         * @param uri   The URI of the authorisation endpoint. May be 
421         *              {@code null} if the {@link #toHTTPRequest()} method
422         *              will not be used.
423         * @param query The URL query string. Must not be {@code null}.
424         *
425         * @return The authorisation request.
426         *
427         * @throws ParseException If the query string couldn't be parsed to an 
428         *                        authorisation request.
429         */
430        public static AuthorizationRequest parse(final URL uri, final String query)
431                throws ParseException {
432        
433                return parse(uri, URLUtils.parseParameters(query));
434        }
435        
436        
437        /**
438         * Parses an authorisation request from the specified HTTP request.
439         *
440         * <p>Example HTTP request (GET):
441         *
442         * <pre>
443         * https://server.example.com/authorize?
444         * response_type=code
445         * &amp;client_id=s6BhdRkqt3
446         * &amp;state=xyz
447         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
448         * </pre>
449         *
450         * @param httpRequest The HTTP request. Must not be {@code null}.
451         *
452         * @return The authorisation request.
453         *
454         * @throws ParseException If the HTTP request couldn't be parsed to an 
455         *                        authorisation request.
456         */
457        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
458                throws ParseException {
459                
460                String query = httpRequest.getQuery();
461                
462                if (query == null)
463                        throw new ParseException("Missing URL query string");
464                
465                return parse(URLUtils.getBaseURL(httpRequest.getURL()), query);
466        }
467}