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 * &code=SplxlOBeZQQYbYS6WxSbIA 044 * &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 }