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