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 code        The authorisation code received from the 
107             *                    authorisation server. Must not be {@code null}.
108             * @param redirectURI The redirect URI, may be {@code null} if 
109             *                    specified in the initial authorisation request.
110             * @param clientID    The client identifier. Must not be {@code null}.
111             */
112            public AccessTokenRequest(final AuthorizationCode code, 
113                                      final URL redirectURI,
114                                      final ClientID clientID) {
115            
116                    super(GrantType.AUTHORIZATION_CODE, null);
117    
118                    if (code == null)
119                            throw new IllegalArgumentException("The authorization code must not be null");
120                    
121                    this.code = code;
122    
123    
124                    this.redirectURI = redirectURI;
125    
126    
127                    if (clientID == null)
128                            throw new IllegalArgumentException("The client ID must not be null");
129    
130                    this.clientID = clientID;
131    
132    
133                    username = null;
134                    password = null;
135                    scope = null;
136            }
137            
138            
139            /**
140             * Creates a new authenticated access token request, using an
141             * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}. 
142             *
143             * @param code        The authorisation code received from the 
144             *                    authorisation server. Must not be {@code null}.
145             * @param redirectURI The redirect URI, may be {@code null} if not
146             *                    specified in the initial authorisation request.
147             * @param clientAuth  The client authentication. Must not be 
148             *                    {@code null}.
149             */
150            public AccessTokenRequest(final AuthorizationCode code, 
151                                      final URL redirectURI, 
152                                      final ClientAuthentication clientAuth) {
153            
154                    super(GrantType.AUTHORIZATION_CODE, clientAuth);
155                    
156                    if (code == null)
157                            throw new IllegalArgumentException("The authorization code must not be null");
158                    
159                    this.code = code;
160                    
161                    this.redirectURI = redirectURI;
162    
163                    if (clientAuth == null)
164                            throw new IllegalArgumentException("The client authentication must not be null");
165    
166    
167                    clientID = null;
168                    username = null;
169                    password = null;
170                    scope = null;
171            }
172    
173    
174            /**
175             * Creates a new authenticated access token request, using a
176             * {@link GrantType#PASSWORD resource owner password credentials grant}.
177             *
178             * @param username The resource owner username. Must not be 
179             *                 {@code null}.
180             * @param password The resource owner password. Must not be 
181             *                 {@code null}.
182             * @param scope    The scope of the access request, {@code null} if not
183             *                 specified.
184             */
185            public AccessTokenRequest(final String username, 
186                                      final String password,
187                                      final Scope scope) {
188            
189                    super(GrantType.PASSWORD, null);
190    
191                    if (username == null)
192                            throw new IllegalArgumentException("The username must not be null");
193    
194                    this.username = username;
195    
196    
197                    if (password == null)
198                            throw new IllegalArgumentException("The password must not be null");
199    
200                    this.password = password;
201    
202                    this.scope = scope;
203    
204                    code = null;
205                    redirectURI = null;
206                    clientID = null;
207            }
208    
209    
210            /**
211             * Creates a new authenticated access token request, using a
212             * {@link GrantType#CLIENT_CREDENTIALS client credentials grant}.
213             *
214             * @param scope      The scope of the access request, {@code null} if 
215             *                   not specified.
216             * @param clientAuth The client authentication. Must not be 
217             *                   {@code null}.
218             */
219            public AccessTokenRequest(final Scope scope, 
220                                      final ClientAuthentication clientAuth) {
221            
222                    super(GrantType.CLIENT_CREDENTIALS, null);
223    
224                    this.scope = scope;
225    
226                    if (clientAuth == null)
227                            throw new IllegalArgumentException("The client authentication must not be null");
228    
229                    code = null;
230                    redirectURI = null;
231                    clientID = null;
232                    username = null;
233                    password = null;
234            }
235            
236            
237            /**
238             * Gets the authorisation code. Applies to requests using an
239             * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}.
240             *
241             * @return The authorisation code, {@code null} if not specified.
242             */
243            public AuthorizationCode getAuthorizationCode() {
244            
245                    return code;
246            }
247            
248            
249            /**
250             * Gets the redirect URI. Applies to requests using an
251             * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}
252             *
253             * @return The redirect URI, {@code null} if not specified.
254             */
255            public URL getRedirectURI() {
256            
257                    return redirectURI;
258            }
259    
260    
261            /**
262             * Gets the client identifier. Applies to requests using an
263             * {@link GrantType#AUTHORIZATION_CODE authorisation code grant}.
264             *
265             * @return The client identifier, {@code null} if not specified.
266             */
267            public ClientID getClientID() {
268    
269                    return clientID;
270            }
271    
272    
273            /**
274             * Gets the resource owner username. Applies to requests using a
275             * {@link GrantType#PASSWORD resource owner password credentials
276             * grant}.
277             *
278             * @return The resource owner username, {@code null} if not specified.
279             */
280            public String getUsername() {
281    
282                    return username;
283            }
284    
285    
286            /**
287             * Gets the resource owner password. Applies to requests using a
288             * {@link GrantType#PASSWORD resource owner password credentials
289             * grant}.
290             *
291             * @return The resource owner password, {@code null} if not specified.
292             */
293            public String getPassword() {
294    
295                    return password;
296            }
297    
298    
299            /**
300             * Gets the access scope. Applies to requests using a
301             * {@link GrantType#PASSWORD resource owner password credentials} or
302             * {@link GrantType#CLIENT_CREDENTIALS client credentials grant}.
303             *
304             * @return The access scope, {@code null} if not specified.
305             */
306            public Scope getScope() {
307    
308                    return scope;
309            }
310            
311            
312            @Override
313            public HTTPRequest toHTTPRequest(final URL url)
314                    throws SerializeException {
315                    
316                    HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
317                    httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
318                    
319                    Map<String,String> params = new LinkedHashMap<String,String>();
320    
321                    params.put("grant_type", getGrantType().toString());
322    
323                    if (getGrantType().equals(GrantType.AUTHORIZATION_CODE)) {
324    
325                            params.put("code", code.toString());
326    
327                            if (redirectURI != null)
328                                    params.put("redirect_uri", redirectURI.toString());
329    
330                            if (clientID != null)
331                                    params.put("client_id", clientID.getValue());
332                    
333                    } else if (getGrantType().equals(GrantType.PASSWORD)) {
334    
335                            params.put("username", username);
336    
337                            params.put("password", password);
338    
339                            if (scope != null)
340                                    params.put("scope", scope.toString());
341    
342                    } else if (getGrantType().equals(GrantType.CLIENT_CREDENTIALS)) {
343    
344                            if (scope != null)
345                                    params.put("scope", scope.toString());
346    
347                    } else {
348    
349                            throw new SerializeException("Unsupported grant type: " + getGrantType());
350                    }
351                    
352                    httpRequest.setQuery(URLUtils.serializeParameters(params));
353                    
354                    if (getClientAuthentication() != null)
355                            getClientAuthentication().applyTo(httpRequest);
356                    
357                    return httpRequest;     
358            }
359            
360            
361            /**
362             * Parses the specified HTTP request for an access token request.
363             *
364             * @param httpRequest The HTTP request. Must not be {@code null}.
365             *
366             * @return The access token request.
367             *
368             * @throws ParseException If the HTTP request couldn't be parsed to an 
369             *                        access token request.
370             */
371            public static AccessTokenRequest parse(final HTTPRequest httpRequest)
372                    throws ParseException {
373                    
374                    // Only HTTP POST accepted
375                    httpRequest.ensureMethod(HTTPRequest.Method.POST);
376                    httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
377                    
378                    // No fragment!
379                    // May use query component!
380                    Map<String,String> params = httpRequest.getQueryParameters();
381                    
382                    
383                    // Parse grant type
384                    String grantTypeString = params.get("grant_type");
385                    
386                    if (grantTypeString == null)
387                            throw new ParseException("Missing \"grant_type\" parameter");
388    
389                    GrantType grantType = new GrantType(grantTypeString);
390                            
391                    if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
392    
393                            // Parse authorisation code
394                            String codeString = params.get("code");
395                    
396                            if (codeString == null)
397                                    throw new ParseException("Missing \"code\" parameter");
398                    
399                            AuthorizationCode code = new AuthorizationCode(codeString);
400                    
401                    
402                            // Parse redirect URI
403                            String redirectURIString = params.get("redirect_uri");
404                            
405                            URL redirectURI = null;
406    
407                            if (redirectURIString != null) {
408                            
409                                    try {
410                                            redirectURI = new URL(redirectURIString);
411                                            
412                                    } catch (MalformedURLException e) {
413                                    
414                                            throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), e);
415                                    }
416                            }
417    
418    
419                            // Parse client ID
420                            String clientIDString = params.get("client_id");
421    
422                            ClientID clientID = null;
423    
424                            if (clientIDString != null)
425                                    clientID = new ClientID(clientIDString);
426    
427                            // Parse client authentication
428                            ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
429    
430                            if (clientAuth != null) {
431    
432                                    // Access token request with client authentication
433                                    return new AccessTokenRequest(code, redirectURI, clientAuth);
434    
435                            } else {
436    
437                                    if (clientID == null)
438                                            throw new ParseException("Missing \"client_id\" parameter");
439    
440                                    // Access token request with no client authentication
441                                    return new AccessTokenRequest(code, redirectURI, clientID);
442                            }
443                    
444                    } else if (grantType.equals(GrantType.PASSWORD)) {
445    
446                            String username = params.get("username");
447    
448                            if (username == null)
449                                    throw new ParseException("Missing \"username\" parameter");
450    
451                            String password = params.get("password");
452    
453                            if (password == null)
454                                    throw new ParseException("Missing \"password\" parameter");
455    
456                            Scope scope = Scope.parse(params.get("scope"));
457    
458                            return new AccessTokenRequest(username, password, scope);
459                    
460                    } else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
461    
462                            Scope scope = Scope.parse(params.get("scope"));
463    
464                            ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
465    
466                            if (clientAuth == null)
467                                    throw new ParseException("Missing client authentication");
468    
469                            return new AccessTokenRequest(scope, clientAuth);
470                                    
471                    } else {
472                            
473                            throw new ParseException("Unsupported grant type: " + grantType);
474                    }
475            }
476    }