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 com.nimbusds.common.contenttype.ContentType; 022import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 023import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; 024import com.nimbusds.oauth2.sdk.http.HTTPRequest; 025import com.nimbusds.oauth2.sdk.id.ClientID; 026import com.nimbusds.oauth2.sdk.rar.AuthorizationDetail; 027import com.nimbusds.oauth2.sdk.token.RefreshToken; 028import com.nimbusds.oauth2.sdk.util.*; 029import net.jcip.annotations.Immutable; 030 031import java.net.URI; 032import java.net.URISyntaxException; 033import java.util.*; 034 035 036/** 037 * Token request. Used to obtain an 038 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an 039 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token} 040 * at the Token endpoint of the authorisation server. Supports custom request 041 * parameters. 042 * 043 * <p>Example token request with an authorisation code grant: 044 * 045 * <pre> 046 * POST /token HTTP/1.1 047 * Host: server.example.com 048 * Content-Type: application/x-www-form-urlencoded 049 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 050 * 051 * grant_type=authorization_code 052 * &code=SplxlOBeZQQYbYS6WxSbIA 053 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 054 * </pre> 055 * 056 * <p>Related specifications: 057 * 058 * <ul> 059 * <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6. 060 * <li>OAuth 2.0 Rich Authorization Requests (RFC 9396), section 6. 061 * <li>Resource Indicators for OAuth 2.0 (RFC 8707) 062 * <li>OAuth 2.0 Incremental Authorization 063 * (draft-ietf-oauth-incremental-authz-04) 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 * The RAR details (optional). 084 */ 085 private final List<AuthorizationDetail> authorizationDetails; 086 087 088 /** 089 * The resource URI(s), {@code null} if not specified. 090 */ 091 private final List<URI> resources; 092 093 094 /** 095 * Existing refresh token for incremental authorisation of a public 096 * client, {@code null} if not specified. 097 */ 098 private final RefreshToken existingGrant; 099 100 101 /** 102 * Custom request parameters. 103 */ 104 private final Map<String,List<String>> customParams; 105 106 107 private static final Set<String> ALLOWED_REPEATED_PARAMS = new HashSet<>(Arrays.asList("resource", "audience")); 108 109 110 /** 111 * Creates a new token request with the specified client 112 * authentication. 113 * 114 * @param uri The URI of the token endpoint. May be 115 * {@code null} if the {@link #toHTTPRequest} method 116 * 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 {@code null}. 120 * @param scope The requested scope, {@code null} if not 121 * specified. 122 */ 123 public TokenRequest(final URI uri, 124 final ClientAuthentication clientAuth, 125 final AuthorizationGrant authzGrant, 126 final Scope scope) { 127 128 this(uri, clientAuth, authzGrant, scope, null, null); 129 } 130 131 132 /** 133 * Creates a new token request with the specified client 134 * authentication and extension and custom parameters. 135 * 136 * @param uri The URI of the token endpoint. May be 137 * {@code null} if the {@link #toHTTPRequest} 138 * method will not be used. 139 * @param clientAuth The client authentication. Must not be 140 * {@code null}. 141 * @param authzGrant The authorisation grant. Must not be 142 * {@code null}. 143 * @param scope The requested scope, {@code null} if not 144 * specified. 145 * @param resources The resource URI(s), {@code null} if not 146 * specified. 147 * @param customParams Custom parameters to be included in the request 148 * body, empty map or {@code null} if none. 149 */ 150 public TokenRequest(final URI uri, 151 final ClientAuthentication clientAuth, 152 final AuthorizationGrant authzGrant, 153 final Scope scope, 154 final List<URI> resources, 155 final Map<String,List<String>> customParams) { 156 157 this(uri, clientAuth, authzGrant, scope, null, resources, customParams); 158 } 159 160 161 /** 162 * Creates a new token request with the specified client 163 * authentication and extension and custom parameters. 164 * 165 * @param uri The URI of the token endpoint. May be 166 * {@code null} if the 167 * {@link #toHTTPRequest} method will not 168 * be used. 169 * @param clientAuth The client authentication. Must not be 170 * {@code null}. 171 * @param authzGrant The authorisation grant. Must not be 172 * {@code null}. 173 * @param scope The requested scope, {@code null} if not 174 * specified. 175 * @param authorizationDetails The Rich Authorisation Request (RAR) 176 * details, {@code null} if not specified. 177 * @param resources The resource URI(s), {@code null} if not 178 * specified. 179 * @param customParams Custom parameters to be included in the 180 * request body, empty map or {@code null} 181 * if none. 182 */ 183 public TokenRequest(final URI uri, 184 final ClientAuthentication clientAuth, 185 final AuthorizationGrant authzGrant, 186 final Scope scope, 187 final List<AuthorizationDetail> authorizationDetails, 188 final List<URI> resources, 189 final Map<String,List<String>> customParams) { 190 191 super(uri, clientAuth); 192 193 if (clientAuth == null) 194 throw new IllegalArgumentException("The client authentication must not be null"); 195 196 this.authzGrant = authzGrant; 197 198 this.scope = scope; 199 200 if (resources != null) { 201 for (URI resourceURI: resources) { 202 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 203 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 204 } 205 } 206 207 this.authorizationDetails = authorizationDetails; 208 209 this.resources = resources; 210 211 this.existingGrant = null; // only for confidential client 212 213 if (MapUtils.isNotEmpty(customParams)) { 214 this.customParams = customParams; 215 } else { 216 this.customParams = Collections.emptyMap(); 217 } 218 } 219 220 221 /** 222 * Creates a new token request with the specified client 223 * authentication. 224 * 225 * @param uri The URI of the token endpoint. May be 226 * {@code null} if the {@link #toHTTPRequest} method 227 * will not be used. 228 * @param clientAuth The client authentication. Must not be 229 * {@code null}. 230 * @param authzGrant The authorisation grant. Must not be {@code null}. 231 */ 232 public TokenRequest(final URI uri, 233 final ClientAuthentication clientAuth, 234 final AuthorizationGrant authzGrant) { 235 236 this(uri, clientAuth, authzGrant, null); 237 } 238 239 240 /** 241 * Creates a new token request, with no explicit client authentication 242 * (maybe present in the grant depending on its type). 243 * 244 * @param uri The URI of the token endpoint. May be 245 * {@code null} if the {@link #toHTTPRequest} method 246 * will not be used. 247 * @param clientID The client identifier, {@code null} if not 248 * specified. 249 * @param authzGrant The authorisation grant. Must not be {@code null}. 250 * @param scope The requested scope, {@code null} if not 251 * specified. 252 */ 253 public TokenRequest(final URI uri, 254 final ClientID clientID, 255 final AuthorizationGrant authzGrant, 256 final Scope scope) { 257 258 this(uri, clientID, authzGrant, scope, null, null,null); 259 } 260 261 262 /** 263 * Creates a new token request, with no explicit client authentication 264 * (maybe present in the grant depending on its type) and extension 265 * and custom parameters. 266 * 267 * @param uri The URI of the token endpoint. May be 268 * {@code null} if the {@link #toHTTPRequest} 269 * method will not be used. 270 * @param clientID The client identifier, {@code null} if not 271 * specified. 272 * @param authzGrant The authorisation grant. Must not be 273 * {@code null}. 274 * @param scope The requested scope, {@code null} if not 275 * specified. 276 * @param resources The resource URI(s), {@code null} if not 277 * specified. 278 * @param existingGrant Existing refresh token for incremental 279 * authorisation of a public client, {@code null} 280 * if not specified. 281 * @param customParams Custom parameters to be included in the request 282 * body, empty map or {@code null} if none. 283 */ 284 public TokenRequest(final URI uri, 285 final ClientID clientID, 286 final AuthorizationGrant authzGrant, 287 final Scope scope, 288 final List<URI> resources, 289 final RefreshToken existingGrant, 290 final Map<String,List<String>> customParams) { 291 292 this(uri, clientID, authzGrant, scope, null, resources, existingGrant, customParams); 293 } 294 295 296 /** 297 * Creates a new token request, with no explicit client authentication 298 * (maybe present in the grant depending on its type) and extension 299 * and custom parameters. 300 * 301 * @param uri The URI of the token endpoint. May be 302 * {@code null} if the 303 * {@link #toHTTPRequest} 304 * method will not be used. 305 * @param clientID The client identifier, {@code null} if 306 * not specified. 307 * @param authzGrant The authorisation grant. Must not be 308 * {@code null}. 309 * @param scope The requested scope, {@code null} if not 310 * specified. 311 * @param authorizationDetails The Rich Authorisation Request (RAR) 312 * details, {@code null} if not specified. 313 * @param resources The resource URI(s), {@code null} if not 314 * specified. 315 * @param existingGrant Existing refresh token for incremental 316 * authorisation of a public client, 317 * {@code null} if not specified. 318 * @param customParams Custom parameters to be included in the 319 * request body, empty map or {@code null} 320 * if none. 321 */ 322 public TokenRequest(final URI uri, 323 final ClientID clientID, 324 final AuthorizationGrant authzGrant, 325 final Scope scope, 326 final List<AuthorizationDetail> authorizationDetails, 327 final List<URI> resources, 328 final RefreshToken existingGrant, 329 final Map<String,List<String>> customParams) { 330 331 super(uri, clientID); 332 333 if (authzGrant.getType().requiresClientAuthentication()) { 334 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication"); 335 } 336 337 if (authzGrant.getType().requiresClientID() && clientID == null) { 338 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter"); 339 } 340 341 this.authzGrant = authzGrant; 342 343 this.scope = scope; 344 345 this.authorizationDetails = authorizationDetails; 346 347 if (resources != null) { 348 for (URI resourceURI: resources) { 349 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 350 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 351 } 352 } 353 354 this.resources = resources; 355 356 this.existingGrant = existingGrant; 357 358 if (MapUtils.isNotEmpty(customParams)) { 359 this.customParams = customParams; 360 } else { 361 this.customParams = Collections.emptyMap(); 362 } 363 } 364 365 366 /** 367 * Creates a new token request, with no explicit client authentication 368 * (maybe present in the grant depending on its type). 369 * 370 * @param uri The URI of the token endpoint. May be 371 * {@code null} if the {@link #toHTTPRequest} method 372 * will not be used. 373 * @param clientID The client identifier, {@code null} if not 374 * specified. 375 * @param authzGrant The authorisation grant. Must not be {@code null}. 376 */ 377 public TokenRequest(final URI uri, 378 final ClientID clientID, 379 final AuthorizationGrant authzGrant) { 380 381 this(uri, clientID, authzGrant, null); 382 } 383 384 385 /** 386 * Creates a new token request, without client authentication and a 387 * specified client identifier. 388 * 389 * @param uri The URI of the token endpoint. May be 390 * {@code null} if the {@link #toHTTPRequest} method 391 * will not be used. 392 * @param authzGrant The authorisation grant. Must not be {@code null}. 393 * @param scope The requested scope, {@code null} if not 394 * specified. 395 */ 396 public TokenRequest(final URI uri, 397 final AuthorizationGrant authzGrant, 398 final Scope scope) { 399 400 this(uri, (ClientID)null, authzGrant, scope); 401 } 402 403 404 /** 405 * Creates a new token request, without client authentication and a 406 * specified client identifier. 407 * 408 * @param uri The URI of the token endpoint. May be 409 * {@code null} if the {@link #toHTTPRequest} method 410 * will not be used. 411 * @param authzGrant The authorisation grant. Must not be {@code null}. 412 */ 413 public TokenRequest(final URI uri, 414 final AuthorizationGrant authzGrant) { 415 416 this(uri, (ClientID)null, authzGrant, null); 417 } 418 419 420 /** 421 * Returns the authorisation grant. 422 * 423 * @return The authorisation grant. 424 */ 425 public AuthorizationGrant getAuthorizationGrant() { 426 427 return authzGrant; 428 } 429 430 431 /** 432 * Returns the requested scope. 433 * 434 * @return The requested scope, {@code null} if not specified. 435 */ 436 public Scope getScope() { 437 438 return scope; 439 } 440 441 442 /** 443 * Returns the Rich Authorisation Request (RAR) details. 444 * 445 * @return The authorisation details, {@code null} if not specified. 446 */ 447 public List<AuthorizationDetail> getAuthorizationDetails() { 448 449 return authorizationDetails; 450 } 451 452 453 /** 454 * Returns the resource server URI. 455 * 456 * @return The resource URI(s), {@code null} if not specified. 457 */ 458 public List<URI> getResources() { 459 460 return resources; 461 } 462 463 464 /** 465 * Returns the existing refresh token for incremental authorisation of 466 * a public client, {@code null} if not specified. 467 * 468 * @return The existing grant, {@code null} if not specified. 469 */ 470 public RefreshToken getExistingGrant() { 471 472 return existingGrant; 473 } 474 475 476 /** 477 * Returns the additional custom parameters included in the request 478 * body. 479 * 480 * <p>Example: 481 * 482 * <pre> 483 * resource=http://xxxxxx/PartyOData 484 * </pre> 485 * 486 * @return The additional custom parameters as an unmodifiable map, 487 * empty map if none. 488 */ 489 public Map<String,List<String>> getCustomParameters () { 490 491 return Collections.unmodifiableMap(customParams); 492 } 493 494 495 /** 496 * Returns the specified custom parameter included in the request body. 497 * 498 * @param name The parameter name. Must not be {@code null}. 499 * 500 * @return The parameter value(s), {@code null} if not specified. 501 */ 502 public List<String> getCustomParameter(final String name) { 503 504 return customParams.get(name); 505 } 506 507 508 @Override 509 public HTTPRequest toHTTPRequest() { 510 511 if (getEndpointURI() == null) 512 throw new SerializeException("The endpoint URI is not specified"); 513 514 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 515 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 516 517 if (getClientAuthentication() != null) { 518 getClientAuthentication().applyTo(httpRequest); 519 } 520 521 Map<String,List<String>> params = httpRequest.getQueryParameters(); 522 523 params.putAll(authzGrant.toParameters()); 524 525 if (scope != null && ! scope.isEmpty()) { 526 params.put("scope", Collections.singletonList(scope.toString())); 527 } 528 529 if (getClientID() != null) { 530 params.put("client_id", Collections.singletonList(getClientID().getValue())); 531 } 532 533 if (getAuthorizationDetails() != null) { 534 params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails()))); 535 } 536 537 if (getResources() != null) { 538 List<String> values = new LinkedList<>(); 539 for (URI uri: resources) { 540 if (uri == null) 541 continue; 542 values.add(uri.toString()); 543 } 544 params.put("resource", values); 545 } 546 547 if (getExistingGrant() != null) { 548 params.put("existing_grant", Collections.singletonList(existingGrant.getValue())); 549 } 550 551 if (! getCustomParameters().isEmpty()) { 552 params.putAll(getCustomParameters()); 553 } 554 555 httpRequest.setQuery(URLUtils.serializeParameters(params)); 556 557 return httpRequest; 558 } 559 560 561 /** 562 * Parses a token request from the specified HTTP request. 563 * 564 * @param httpRequest The HTTP request. Must not be {@code null}. 565 * 566 * @return The token request. 567 * 568 * @throws ParseException If the HTTP request couldn't be parsed to a 569 * token request. 570 */ 571 public static TokenRequest parse(final HTTPRequest httpRequest) 572 throws ParseException { 573 574 // Only HTTP POST accepted 575 URI uri = httpRequest.getURI(); 576 httpRequest.ensureMethod(HTTPRequest.Method.POST); 577 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 578 579 // Parse client authentication, if any 580 ClientAuthentication clientAuth; 581 582 try { 583 clientAuth = ClientAuthentication.parse(httpRequest); 584 } catch (ParseException e) { 585 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 586 } 587 588 // No fragment! May use query component! 589 Map<String,List<String>> params = httpRequest.getQueryParameters(); 590 591 Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS); 592 if (! repeatParams.isEmpty()) { 593 String msg = "Parameter(s) present more than once: " + repeatParams; 594 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg)); 595 } 596 597 // Multiple conflicting client auth methods (issue #203)? 598 if (clientAuth instanceof ClientSecretBasic) { 599 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 600 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 601 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 602 } 603 } 604 605 // Parse grant 606 AuthorizationGrant grant = AuthorizationGrant.parse(params); 607 608 if (clientAuth == null && grant.getType().requiresClientAuthentication()) { 609 String msg = "Missing client authentication"; 610 throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg)); 611 } 612 613 // Parse client id 614 ClientID clientID = null; 615 616 if (clientAuth == null) { 617 618 // Parse optional client ID 619 String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id"); 620 621 if (clientIDString != null && ! clientIDString.trim().isEmpty()) 622 clientID = new ClientID(clientIDString); 623 624 if (clientID == null && grant.getType().requiresClientID()) { 625 String msg = "Missing required client_id parameter"; 626 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 627 } 628 } 629 630 // Parse optional scope 631 String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope"); 632 633 Scope scope = null; 634 635 if (scopeValue != null) { 636 scope = Scope.parse(scopeValue); 637 } 638 639 // Parse optional RAR 640 String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details"); 641 642 List<AuthorizationDetail> authorizationDetails = null; 643 644 if (json != null) { 645 authorizationDetails = AuthorizationDetail.parseList(json); 646 } 647 648 // Parse optional resource URIs 649 List<URI> resources = null; 650 651 List<String> vList = params.get("resource"); 652 653 if (vList != null) { 654 655 resources = new LinkedList<>(); 656 657 for (String uriValue: vList) { 658 659 if (uriValue == null) 660 continue; 661 662 String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue; 663 664 URI resourceURI; 665 try { 666 resourceURI = new URI(uriValue); 667 } catch (URISyntaxException e) { 668 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 669 } 670 671 if (! ResourceUtils.isLegalResourceURI(resourceURI)) { 672 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 673 } 674 675 resources.add(resourceURI); 676 } 677 } 678 679 String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant"); 680 RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null; 681 682 // Parse custom parameters 683 Map<String,List<String>> customParams = new HashMap<>(); 684 685 for (Map.Entry<String,List<String>> p: params.entrySet()) { 686 687 if (p.getKey().equalsIgnoreCase("grant_type")) { 688 continue; // skip 689 } 690 691 if (p.getKey().equalsIgnoreCase("client_id")) { 692 continue; // skip 693 } 694 695 if (p.getKey().equalsIgnoreCase("client_secret")) { 696 continue; // skip 697 } 698 699 if (p.getKey().equalsIgnoreCase("client_assertion_type")) { 700 continue; // skip 701 } 702 703 if (p.getKey().equalsIgnoreCase("client_assertion")) { 704 continue; // skip 705 } 706 707 if (p.getKey().equalsIgnoreCase("scope")) { 708 continue; // skip 709 } 710 711 if (p.getKey().equalsIgnoreCase("authorization_details")) { 712 continue; // skip 713 } 714 715 if (p.getKey().equalsIgnoreCase("resource")) { 716 continue; // skip 717 } 718 719 if (p.getKey().equalsIgnoreCase("existing_grant")) 720 continue; // skip 721 722 if (! grant.getType().getRequestParameterNames().contains(p.getKey())) { 723 // We have a custom (non-registered) parameter 724 customParams.put(p.getKey(), p.getValue()); 725 } 726 } 727 728 if (clientAuth != null) { 729 return new TokenRequest(uri, clientAuth, grant, scope, authorizationDetails, resources, customParams); 730 } else { 731 // public client 732 return new TokenRequest(uri, clientID, grant, scope, authorizationDetails, resources, existingGrant, customParams); 733 } 734 } 735}