001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk; 019 020 021import java.net.MalformedURLException; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.net.URL; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Map; 028 029import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 030import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; 031import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.id.ClientID; 034import com.nimbusds.oauth2.sdk.util.MapUtils; 035import com.nimbusds.oauth2.sdk.util.StringUtils; 036import com.nimbusds.oauth2.sdk.util.URLUtils; 037import net.jcip.annotations.Immutable; 038 039 040/** 041 * Token request. Used to obtain an 042 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an 043 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token} 044 * at the Token endpoint of the authorisation server. Supports custom request 045 * parameters. 046 * 047 * <p>Example token request with an authorisation code grant: 048 * 049 * <pre> 050 * POST /token HTTP/1.1 051 * Host: server.example.com 052 * Content-Type: application/x-www-form-URIencoded 053 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 054 * 055 * grant_type=authorization_code 056 * &code=SplxlOBeZQQYbYS6WxSbIA 057 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 058 * </pre> 059 * 060 * <p>Related specifications: 061 * 062 * <ul> 063 * <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6. 064 * </ul> 065 */ 066@Immutable 067public class TokenRequest extends AbstractOptionallyIdentifiedRequest { 068 069 070 /** 071 * The authorisation grant. 072 */ 073 private final AuthorizationGrant authzGrant; 074 075 076 /** 077 * The requested scope, {@code null} if not specified. 078 */ 079 private final Scope scope; 080 081 082 /** 083 * Additional custom request parameters. 084 */ 085 private final Map<String,String> customParams; 086 087 088 /** 089 * Creates a new token request with the specified client 090 * authentication. 091 * 092 * @param uri The URI of the token endpoint. May be 093 * {@code null} if the {@link #toHTTPRequest} method 094 * will not be used. 095 * @param clientAuth The client authentication. Must not be 096 * {@code null}. 097 * @param authzGrant The authorisation grant. Must not be {@code null}. 098 * @param scope The requested scope, {@code null} if not 099 * specified. 100 */ 101 public TokenRequest(final URI uri, 102 final ClientAuthentication clientAuth, 103 final AuthorizationGrant authzGrant, 104 final Scope scope) { 105 106 this(uri, clientAuth, authzGrant, scope, null); 107 } 108 109 110 /** 111 * Creates a new token request with the specified client 112 * authentication and additional custom parameters. 113 * 114 * @param uri The URI of the token endpoint. May be 115 * {@code null} if the {@link #toHTTPRequest} 116 * method will not be used. 117 * @param clientAuth The client authentication. Must not be 118 * {@code null}. 119 * @param authzGrant The authorisation grant. Must not be 120 * {@code null}. 121 * @param scope The requested scope, {@code null} if not 122 * specified. 123 * @param customParams Additional custom parameters to be included in 124 * the request body, empty map or {@code null} if 125 * none. 126 */ 127 public TokenRequest(final URI uri, 128 final ClientAuthentication clientAuth, 129 final AuthorizationGrant authzGrant, 130 final Scope scope, 131 final Map<String,String> customParams) { 132 133 super(uri, clientAuth); 134 135 if (clientAuth == null) 136 throw new IllegalArgumentException("The client authentication must not be null"); 137 138 this.authzGrant = authzGrant; 139 140 this.scope = scope; 141 142 if (MapUtils.isNotEmpty(customParams)) { 143 this.customParams = customParams; 144 } else { 145 this.customParams = Collections.emptyMap(); 146 } 147 } 148 149 150 /** 151 * Creates a new token request with the specified client 152 * authentication. 153 * 154 * @param uri The URI of the token endpoint. May be 155 * {@code null} if the {@link #toHTTPRequest} method 156 * will not be used. 157 * @param clientAuth The client authentication. Must not be 158 * {@code null}. 159 * @param authzGrant The authorisation grant. Must not be {@code null}. 160 */ 161 public TokenRequest(final URI uri, 162 final ClientAuthentication clientAuth, 163 final AuthorizationGrant authzGrant) { 164 165 this(uri, clientAuth, authzGrant, null); 166 } 167 168 169 /** 170 * Creates a new token request, with no explicit client authentication 171 * (may be present in the grant depending on its type). 172 * 173 * @param uri The URI of the token endpoint. May be 174 * {@code null} if the {@link #toHTTPRequest} method 175 * will not be used. 176 * @param clientID The client identifier, {@code null} if not 177 * specified. 178 * @param authzGrant The authorisation grant. Must not be {@code null}. 179 * @param scope The requested scope, {@code null} if not 180 * specified. 181 */ 182 public TokenRequest(final URI uri, 183 final ClientID clientID, 184 final AuthorizationGrant authzGrant, 185 final Scope scope) { 186 187 this(uri, clientID, authzGrant, scope, null); 188 } 189 190 191 /** 192 * Creates a new token request, with no explicit client authentication 193 * (may be present in the grant depending on its type) and additional 194 * custom parameters. 195 * 196 * @param uri The URI of the token endpoint. May be 197 * {@code null} if the {@link #toHTTPRequest} 198 * method will not be used. 199 * @param clientID The client identifier, {@code null} if not 200 * specified. 201 * @param authzGrant The authorisation grant. Must not be 202 * {@code null}. 203 * @param scope The requested scope, {@code null} if not 204 * specified. 205 * @param customParams Additional custom parameters to be included in 206 * the request body, empty map or {@code null} if 207 * none. 208 */ 209 public TokenRequest(final URI uri, 210 final ClientID clientID, 211 final AuthorizationGrant authzGrant, 212 final Scope scope, 213 final Map<String,String> customParams) { 214 215 super(uri, clientID); 216 217 if (authzGrant.getType().requiresClientAuthentication()) { 218 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication"); 219 } 220 221 if (authzGrant.getType().requiresClientID() && clientID == null) { 222 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter"); 223 } 224 225 this.authzGrant = authzGrant; 226 227 this.scope = scope; 228 229 if (MapUtils.isNotEmpty(customParams)) { 230 this.customParams = customParams; 231 } else { 232 this.customParams = Collections.emptyMap(); 233 } 234 } 235 236 237 /** 238 * Creates a new token request, with no explicit client authentication 239 * (may be present in the grant depending on its type). 240 * 241 * @param uri The URI of the token endpoint. May be 242 * {@code null} if the {@link #toHTTPRequest} method 243 * will not be used. 244 * @param clientID The client identifier, {@code null} if not 245 * specified. 246 * @param authzGrant The authorisation grant. Must not be {@code null}. 247 */ 248 public TokenRequest(final URI uri, 249 final ClientID clientID, 250 final AuthorizationGrant authzGrant) { 251 252 this(uri, clientID, authzGrant, null); 253 } 254 255 256 /** 257 * Creates a new token request, without client authentication and a 258 * specified client identifier. 259 * 260 * @param uri The URI of the token endpoint. May be 261 * {@code null} if the {@link #toHTTPRequest} method 262 * will not be used. 263 * @param authzGrant The authorisation grant. Must not be {@code null}. 264 * @param scope The requested scope, {@code null} if not 265 * specified. 266 */ 267 public TokenRequest(final URI uri, 268 final AuthorizationGrant authzGrant, 269 final Scope scope) { 270 271 this(uri, (ClientID)null, authzGrant, scope); 272 } 273 274 275 /** 276 * Creates a new token request, without client authentication and a 277 * specified client identifier. 278 * 279 * @param uri The URI of the token endpoint. May be 280 * {@code null} if the {@link #toHTTPRequest} method 281 * will not be used. 282 * @param authzGrant The authorisation grant. Must not be {@code null}. 283 */ 284 public TokenRequest(final URI uri, 285 final AuthorizationGrant authzGrant) { 286 287 this(uri, (ClientID)null, authzGrant, null); 288 } 289 290 291 /** 292 * Gets the authorisation grant. 293 * 294 * @return The authorisation grant. 295 */ 296 public AuthorizationGrant getAuthorizationGrant() { 297 298 return authzGrant; 299 } 300 301 302 /** 303 * Gets the requested scope. 304 * 305 * @return The requested scope, {@code null} if not specified. 306 */ 307 public Scope getScope() { 308 309 return scope; 310 } 311 312 313 /** 314 * Returns the additional custom parameters included in the request 315 * body. 316 * 317 * <p>Example: 318 * 319 * <pre> 320 * resource=http://xxxxxx/PartyOData 321 * </pre> 322 * 323 * @return The additional custom parameters as a unmodifiable map, 324 * empty map if none. 325 */ 326 public Map<String,String> getCustomParameters () { 327 328 return customParams; 329 } 330 331 332 /** 333 * Returns the specified custom parameter included in the request body. 334 * 335 * @param name The parameter name. Must not be {@code null}. 336 * 337 * @return The parameter value, {@code null} if not specified. 338 */ 339 public String getCustomParameter(final String name) { 340 341 return customParams.get(name); 342 } 343 344 345 @Override 346 public HTTPRequest toHTTPRequest() { 347 348 if (getEndpointURI() == null) 349 throw new SerializeException("The endpoint URI is not specified"); 350 351 URL url; 352 353 try { 354 url = getEndpointURI().toURL(); 355 356 } catch (MalformedURLException e) { 357 358 throw new SerializeException(e.getMessage(), e); 359 } 360 361 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url); 362 httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 363 364 if (getClientAuthentication() != null) { 365 getClientAuthentication().applyTo(httpRequest); 366 } 367 368 Map<String,String> params = httpRequest.getQueryParameters(); 369 370 params.putAll(authzGrant.toParameters()); 371 372 if (scope != null && ! scope.isEmpty()) { 373 params.put("scope", scope.toString()); 374 } 375 376 if (getClientID() != null) { 377 params.put("client_id", getClientID().getValue()); 378 } 379 380 if (! getCustomParameters().isEmpty()) { 381 params.putAll(getCustomParameters()); 382 } 383 384 httpRequest.setQuery(URLUtils.serializeParameters(params)); 385 386 return httpRequest; 387 } 388 389 390 /** 391 * Parses a token request from the specified HTTP request. 392 * 393 * @param httpRequest The HTTP request. Must not be {@code null}. 394 * 395 * @return The token request. 396 * 397 * @throws ParseException If the HTTP request couldn't be parsed to a 398 * token request. 399 */ 400 public static TokenRequest parse(final HTTPRequest httpRequest) 401 throws ParseException { 402 403 // Only HTTP POST accepted 404 URI uri; 405 406 try { 407 uri = httpRequest.getURL().toURI(); 408 409 } catch (URISyntaxException e) { 410 411 throw new ParseException(e.getMessage(), e); 412 } 413 414 httpRequest.ensureMethod(HTTPRequest.Method.POST); 415 httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED); 416 417 // Parse client authentication, if any 418 ClientAuthentication clientAuth; 419 420 try { 421 clientAuth = ClientAuthentication.parse(httpRequest); 422 } catch (ParseException e) { 423 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 424 } 425 426 // No fragment! May use query component! 427 Map<String,String> params = httpRequest.getQueryParameters(); 428 429 // Multiple conflicting client auth methods (issue #203)? 430 if (clientAuth instanceof ClientSecretBasic) { 431 if (StringUtils.isNotBlank(params.get("client_assertion")) || StringUtils.isNotBlank(params.get("client_assertion_type"))) { 432 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 433 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 434 } 435 } 436 437 // Parse grant 438 AuthorizationGrant grant = AuthorizationGrant.parse(params); 439 440 if (clientAuth == null && grant.getType().requiresClientAuthentication()) { 441 String msg = "Missing client authentication"; 442 throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg)); 443 } 444 445 // Parse client id 446 ClientID clientID = null; 447 448 if (clientAuth == null) { 449 450 // Parse optional client ID 451 String clientIDString = params.get("client_id"); 452 453 if (clientIDString != null && ! clientIDString.trim().isEmpty()) 454 clientID = new ClientID(clientIDString); 455 456 if (clientID == null && grant.getType().requiresClientID()) { 457 String msg = "Missing required \"client_id\" parameter"; 458 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 459 } 460 } 461 462 // Parse optional scope 463 String scopeValue = params.get("scope"); 464 465 Scope scope = null; 466 467 if (scopeValue != null) { 468 scope = Scope.parse(scopeValue); 469 } 470 471 // Parse custom parameters 472 Map<String,String> customParams = new HashMap<>(); 473 474 for (Map.Entry<String,String> p: params.entrySet()) { 475 476 if (p.getKey().equalsIgnoreCase("grant_type")) { 477 continue; // skip 478 } 479 480 if (p.getKey().equalsIgnoreCase("client_id")) { 481 continue; // skip 482 } 483 484 if (p.getKey().equalsIgnoreCase("client_secret")) { 485 continue; // skip 486 } 487 488 if (p.getKey().equalsIgnoreCase("client_assertion_type")) { 489 continue; // skip 490 } 491 492 if (p.getKey().equalsIgnoreCase("client_assertion")) { 493 continue; // skip 494 } 495 496 if (p.getKey().equalsIgnoreCase("scope")) { 497 continue; // skip 498 } 499 500 if (! grant.getType().getRequestParameterNames().contains(p.getKey())) { 501 // We have a custom (non-registered) parameter 502 customParams.put(p.getKey(), p.getValue()); 503 } 504 } 505 506 if (clientAuth != null) { 507 return new TokenRequest(uri, clientAuth, grant, scope, customParams); 508 } else { 509 return new TokenRequest(uri, clientID, grant, scope, customParams); 510 } 511 } 512}