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