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 com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
012import com.nimbusds.oauth2.sdk.id.ClientID;
013import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
014import com.nimbusds.oauth2.sdk.http.HTTPRequest;
015import com.nimbusds.oauth2.sdk.util.URLUtils;
016
017
018/**
019 * Access token request to the Token endpoint. Used to obtain an 
020 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an 
021 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token} 
022 * from the authorisation server. This class is immutable.
023 *
024 * <p>Supported authorisation grant types:
025 *
026 * <ul>
027 *     <li>{@link GrantType#AUTHORIZATION_CODE Authorisation code}
028 *     <li>{@link GrantType#PASSWORD Resource owner password credentials}
029 *     <li>{@link GrantType#CLIENT_CREDENTIALS Client credentials}
030 * </ul>
031 *
032 * <p>Example HTTP request, with 
033 * {@link com.nimbusds.oauth2.sdk.auth.ClientSecretBasic client secret basic}
034 * authentication:
035 *
036 * <pre>
037 * POST /token HTTP/1.1
038 * Host: server.example.com
039 * Content-Type: application/x-www-form-urlencoded
040 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
041 * 
042 * grant_type=authorization_code
043 * &amp;code=SplxlOBeZQQYbYS6WxSbIA
044 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
045 * </pre>
046 *
047 * <p>Related specifications:
048 *
049 * <ul>
050 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2 and 4.4.2.
051 * </ul>
052 *
053 * @author Vladimir Dzhuvinov
054 */
055@Immutable
056public final class AccessTokenRequest extends TokenRequest {
057        
058        
059        // Authorisation code grant
060
061        /**
062         * The authorisation code received from the authorisation server.
063         */
064        private final AuthorizationCode code;
065        
066        
067        /**
068         * The conditionally required redirect URI in the initial authorisation
069         * request.
070         */
071        private final URL redirectURI;
072
073
074        /**
075         * The conditionally required client ID.
076         */
077        private final ClientID clientID;
078
079
080        // Password credentials grant
081
082        /**
083         * The username.
084         */
085        private final String username;
086
087
088        /**
089         * The password.
090         */
091        private final String password;
092
093
094        // For password + client credentials grant
095
096        /**
097         * The access scope.
098         */
099        private final Scope scope;
100        
101        
102        /**
103         * Creates a new unauthenticated access token request, using an
104         * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}.
105         *
106         * @param uri         The URI of the token endpoint. May be 
107         *                    {@code null} if the {@link #toHTTPRequest()}
108         *                    method will not be used.
109         * @param code        The authorisation code received from the 
110         *                    authorisation server. Must not be {@code null}.
111         * @param redirectURI The redirect URI, may be {@code null} if 
112         *                    specified in the initial authorisation request.
113         * @param clientID    The client identifier. Must not be {@code null}.
114         */
115        public AccessTokenRequest(final URL uri,
116                                  final AuthorizationCode code, 
117                                  final URL redirectURI,
118                                  final ClientID clientID) {
119        
120                super(uri, GrantType.AUTHORIZATION_CODE, null);
121
122                if (code == null)
123                        throw new IllegalArgumentException("The authorization code must not be null");
124                
125                this.code = code;
126
127
128                this.redirectURI = redirectURI;
129
130
131                if (clientID == null)
132                        throw new IllegalArgumentException("The client ID must not be null");
133
134                this.clientID = clientID;
135
136
137                username = null;
138                password = null;
139                scope = null;
140        }
141        
142        
143        /**
144         * Creates a new authenticated access token request, using an
145         * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}. 
146         *
147         * @param uri         The URI of the token endpoint. May be 
148         *                    {@code null} if the {@link #toHTTPRequest()}
149         *                    method will not be used.
150         * @param code        The authorisation code received from the 
151         *                    authorisation server. Must not be {@code null}.
152         * @param redirectURI The redirect URI, may be {@code null} if not
153         *                    specified in the initial authorisation request.
154         * @param clientAuth  The client authentication. Must not be 
155         *                    {@code null}.
156         */
157        public AccessTokenRequest(final URL uri,
158                                  final AuthorizationCode code, 
159                                  final URL redirectURI, 
160                                  final ClientAuthentication clientAuth) {
161        
162                super(uri, GrantType.AUTHORIZATION_CODE, clientAuth);
163                
164                if (code == null)
165                        throw new IllegalArgumentException("The authorization code must not be null");
166                
167                this.code = code;
168                
169                this.redirectURI = redirectURI;
170
171                if (clientAuth == null)
172                        throw new IllegalArgumentException("The client authentication must not be null");
173                
174                clientID = null;
175                username = null;
176                password = null;
177                scope = null;
178        }
179
180
181        /**
182         * Creates a new authenticated access token request, using a
183         * {@link GrantType#PASSWORD resource owner password credentials 
184         * grant}.
185         *
186         * @param uri      The URI of the token endpoint. May be {@code null} 
187         *                 if the {@link #toHTTPRequest()} method will not be 
188         *                 used.
189         * @param username The resource owner username. Must not be 
190         *                 {@code null}.
191         * @param password The resource owner password. Must not be 
192         *                 {@code null}.
193         * @param scope    The scope of the access request, {@code null} if not
194         *                 specified.
195         */
196        public AccessTokenRequest(final URL uri,
197                                  final String username, 
198                                  final String password,
199                                  final Scope scope) {
200        
201                super(uri, GrantType.PASSWORD, null);
202
203                if (username == null)
204                        throw new IllegalArgumentException("The username must not be null");
205
206                this.username = username;
207
208
209                if (password == null)
210                        throw new IllegalArgumentException("The password must not be null");
211
212                this.password = password;
213
214                this.scope = scope;
215
216                code = null;
217                redirectURI = null;
218                clientID = null;
219        }
220
221
222        /**
223         * Creates a new authenticated access token request, using a
224         * {@link GrantType#CLIENT_CREDENTIALS client credentials grant}.
225         *
226         * @param uri        The URI of the token endpoint. May be 
227         *                   {@code null} if the {@link #toHTTPRequest()}
228         *                   method will not be used.
229         * @param scope      The scope of the access request, {@code null} if 
230         *                   not specified.
231         * @param clientAuth The client authentication. Must not be 
232         *                   {@code null}.
233         */
234        public AccessTokenRequest(final URL uri,
235                                  final Scope scope, 
236                                  final ClientAuthentication clientAuth) {
237        
238                super(uri, GrantType.CLIENT_CREDENTIALS, null);
239
240                this.scope = scope;
241
242                if (clientAuth == null)
243                        throw new IllegalArgumentException("The client authentication must not be null");
244
245                code = null;
246                redirectURI = null;
247                clientID = null;
248                username = null;
249                password = null;
250        }
251        
252        
253        /**
254         * Gets the authorisation code. Applies to requests using an
255         * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}.
256         *
257         * @return The authorisation code, {@code null} if not specified.
258         */
259        public AuthorizationCode getAuthorizationCode() {
260        
261                return code;
262        }
263        
264        
265        /**
266         * Gets the redirect URI. Applies to requests using an
267         * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}
268         *
269         * @return The redirect URI, {@code null} if not specified.
270         */
271        public URL getRedirectURI() {
272        
273                return redirectURI;
274        }
275
276
277        /**
278         * Gets the client identifier. Applies to requests using an
279         * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}.
280         *
281         * @return The client identifier, {@code null} if not specified.
282         */
283        public ClientID getClientID() {
284
285                return clientID;
286        }
287
288
289        /**
290         * Gets the resource owner username. Applies to requests using a
291         * {@link GrantType#PASSWORD resource owner password credentials
292         * grant}.
293         *
294         * @return The resource owner username, {@code null} if not specified.
295         */
296        public String getUsername() {
297
298                return username;
299        }
300
301
302        /**
303         * Gets the resource owner password. Applies to requests using a
304         * {@link GrantType#PASSWORD resource owner password credentials
305         * grant}.
306         *
307         * @return The resource owner password, {@code null} if not specified.
308         */
309        public String getPassword() {
310
311                return password;
312        }
313
314
315        /**
316         * Gets the access scope. Applies to requests using a
317         * {@link GrantType#PASSWORD resource owner password credentials} or
318         * {@link GrantType#CLIENT_CREDENTIALS client credentials grant}.
319         *
320         * @return The access scope, {@code null} if not specified.
321         */
322        public Scope getScope() {
323
324                return scope;
325        }
326        
327        
328        @Override
329        public HTTPRequest toHTTPRequest()
330                throws SerializeException {
331                
332                if (getURI() == null)
333                        throw new SerializeException("The endpoint URI is not specified");
334                
335                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getURI());
336                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
337                
338                Map<String,String> params = new LinkedHashMap<String,String>();
339
340                params.put("grant_type", getGrantType().toString());
341
342                if (getGrantType().equals(GrantType.AUTHORIZATION_CODE)) {
343
344                        params.put("code", code.toString());
345
346                        if (redirectURI != null)
347                                params.put("redirect_uri", redirectURI.toString());
348
349                        if (clientID != null)
350                                params.put("client_id", clientID.getValue());
351                
352                } else if (getGrantType().equals(GrantType.PASSWORD)) {
353
354                        params.put("username", username);
355
356                        params.put("password", password);
357
358                        if (scope != null)
359                                params.put("scope", scope.toString());
360
361                } else if (getGrantType().equals(GrantType.CLIENT_CREDENTIALS)) {
362
363                        if (scope != null)
364                                params.put("scope", scope.toString());
365
366                } else {
367
368                        throw new SerializeException("Unsupported grant type: " + getGrantType());
369                }
370                
371                httpRequest.setQuery(URLUtils.serializeParameters(params));
372                
373                if (getClientAuthentication() != null)
374                        getClientAuthentication().applyTo(httpRequest);
375                
376                return httpRequest;     
377        }
378        
379        
380        /**
381         * Parses the specified HTTP request for an access token request.
382         *
383         * @param httpRequest The HTTP request. Must not be {@code null}.
384         *
385         * @return The access token request.
386         *
387         * @throws ParseException If the HTTP request couldn't be parsed to an 
388         *                        access token request.
389         */
390        public static AccessTokenRequest parse(final HTTPRequest httpRequest)
391                throws ParseException {
392                
393                // Only HTTP POST accepted
394                httpRequest.ensureMethod(HTTPRequest.Method.POST);
395                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
396                
397                // No fragment!
398                // May use query component!
399                Map<String,String> params = httpRequest.getQueryParameters();
400                
401                
402                // Parse grant type
403                String grantTypeString = params.get("grant_type");
404                
405                if (grantTypeString == null)
406                        throw new ParseException("Missing \"grant_type\" parameter");
407
408                GrantType grantType = new GrantType(grantTypeString);
409                        
410                if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
411
412                        // Parse authorisation code
413                        String codeString = params.get("code");
414                
415                        if (codeString == null)
416                                throw new ParseException("Missing \"code\" parameter");
417                
418                        AuthorizationCode code = new AuthorizationCode(codeString);
419                
420                
421                        // Parse redirect URI
422                        String redirectURIString = params.get("redirect_uri");
423                        
424                        URL redirectURI = null;
425
426                        if (redirectURIString != null) {
427                        
428                                try {
429                                        redirectURI = new URL(redirectURIString);
430                                        
431                                } catch (MalformedURLException e) {
432                                
433                                        throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), e);
434                                }
435                        }
436
437
438                        // Parse client ID
439                        String clientIDString = params.get("client_id");
440
441                        ClientID clientID = null;
442
443                        if (clientIDString != null)
444                                clientID = new ClientID(clientIDString);
445
446                        // Parse client authentication
447                        ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
448
449                        if (clientAuth != null) {
450
451                                // Access token request with client authentication
452                                return new AccessTokenRequest(URLUtils.getBaseURL(httpRequest.getURL()), code, redirectURI, clientAuth);
453
454                        } else {
455
456                                if (clientID == null)
457                                        throw new ParseException("Missing \"client_id\" parameter");
458
459                                // Access token request with no client authentication
460                                return new AccessTokenRequest(URLUtils.getBaseURL(httpRequest.getURL()), code, redirectURI, clientID);
461                        }
462                
463                } else if (grantType.equals(GrantType.PASSWORD)) {
464
465                        String username = params.get("username");
466
467                        if (username == null)
468                                throw new ParseException("Missing \"username\" parameter");
469
470                        String password = params.get("password");
471
472                        if (password == null)
473                                throw new ParseException("Missing \"password\" parameter");
474
475                        Scope scope = Scope.parse(params.get("scope"));
476
477                        return new AccessTokenRequest(URLUtils.getBaseURL(httpRequest.getURL()), username, password, scope);
478                
479                } else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
480
481                        Scope scope = Scope.parse(params.get("scope"));
482
483                        ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
484
485                        if (clientAuth == null)
486                                throw new ParseException("Missing client authentication");
487
488                        return new AccessTokenRequest(URLUtils.getBaseURL(httpRequest.getURL()), scope, clientAuth);
489                                
490                } else {
491                        
492                        throw new ParseException("Unsupported grant type: " + grantType);
493                }
494        }
495}