001    package com.nimbusds.oauth2.sdk;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    import java.util.LinkedHashMap;
007    import java.util.Map;
008    
009    import net.jcip.annotations.Immutable;
010    
011    import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
012    import com.nimbusds.oauth2.sdk.id.ClientID;
013    import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
014    import com.nimbusds.oauth2.sdk.http.HTTPRequest;
015    import 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
056    public 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    }