001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2023, 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.http.HTTPRequest; 024import com.nimbusds.oauth2.sdk.id.ClientID; 025import com.nimbusds.oauth2.sdk.rar.AuthorizationDetail; 026import com.nimbusds.oauth2.sdk.token.AccessToken; 027import com.nimbusds.oauth2.sdk.token.RefreshToken; 028import com.nimbusds.oauth2.sdk.util.*; 029import com.nimbusds.openid.connect.sdk.nativesso.DeviceSecret; 030import net.jcip.annotations.Immutable; 031 032import java.net.URI; 033import java.net.URISyntaxException; 034import java.util.*; 035 036 037/** 038 * Token request. Used to obtain an {@link AccessToken access token} and an 039 * optional {@link RefreshToken refresh token} at the tokens endpoint of an 040 * authorisation server. Supports custom request parameters. 041 * 042 * <p>Example token request with an authorisation code grant: 043 * 044 * <pre> 045 * POST /token HTTP/1.1 046 * Host: server.example.com 047 * Content-Type: application/x-www-form-urlencoded 048 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 049 * 050 * grant_type=authorization_code 051 * &code=SplxlOBeZQQYbYS6WxSbIA 052 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 053 * </pre> 054 * 055 * <p>Related specifications: 056 * 057 * <ul> 058 * <li>OAuth 2.0 (RFC 6749) 059 * <li>OAuth 2.0 Rich Authorization Requests (RFC 9396) 060 * <li>Resource Indicators for OAuth 2.0 (RFC 8707) 061 * <li>OAuth 2.0 Incremental Authorization (draft-ietf-oauth-incremental-authz) 062 * <li>OpenID Connect Native SSO for Mobile Apps 1.0 063 * </ul> 064 */ 065@Immutable 066public class TokenRequest extends AbstractOptionallyIdentifiedRequest { 067 068 069 /** 070 * The registered parameter names. 071 */ 072 private static final Set<String> REGISTERED_PARAMETER_NAMES; 073 074 static { 075 Set<String> p = new HashSet<>(); 076 077 p.add("grant_type"); 078 p.add("client_id"); 079 p.add("client_secret"); 080 p.add("client_assertion_type"); 081 p.add("client_assertion"); 082 p.add("scope"); 083 p.add("authorization_details"); 084 p.add("resource"); 085 p.add("existing_grant"); 086 p.add("device_secret"); 087 088 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 089 } 090 091 092 /** 093 * The authorisation grant. 094 */ 095 private final AuthorizationGrant authzGrant; 096 097 098 /** 099 * The scope (optional). 100 */ 101 private final Scope scope; 102 103 104 /** 105 * The RAR details (optional). 106 */ 107 private final List<AuthorizationDetail> authorizationDetails; 108 109 110 /** 111 * The resource URI(s) (optional). 112 */ 113 private final List<URI> resources; 114 115 116 /** 117 * Existing refresh token for incremental authorisation of a public 118 * client (optional). 119 */ 120 private final RefreshToken existingGrant; 121 122 123 /** 124 * Device secret for native SSO (optional). 125 */ 126 private final DeviceSecret deviceSecret; 127 128 129 /** 130 * Custom request parameters. 131 */ 132 private final Map<String,List<String>> customParams; 133 134 135 private static final Set<String> ALLOWED_REPEATED_PARAMS = new HashSet<>(Arrays.asList( 136 "resource", // https://www.rfc-editor.org/rfc/rfc8707.html#section-2.2 137 "audience" // https://www.rfc-editor.org/rfc/rfc8693.html#name-relationship-between-resour 138 )); 139 140 141 /** 142 * Builder for constructing token requests. 143 */ 144 public static class Builder { 145 146 147 /** 148 * The endpoint URI (optional). 149 */ 150 private final URI endpoint; 151 152 153 /** 154 * The client authentication candidates, {@code null} if none. 155 */ 156 private final List<ClientAuthentication> clientAuthCandidates; 157 158 159 /** 160 * The client identifier, {@code null} if not specified. 161 */ 162 private final ClientID clientID; 163 164 165 /** 166 * The authorisation grant. 167 */ 168 private final AuthorizationGrant authzGrant; 169 170 171 /** 172 * The scope (optional). 173 */ 174 private Scope scope; 175 176 177 /** 178 * The RAR details (optional). 179 */ 180 private List<AuthorizationDetail> authorizationDetails; 181 182 183 /** 184 * The resource URI(s) (optional). 185 */ 186 private List<URI> resources; 187 188 189 /** 190 * Existing refresh token for incremental authorisation of a 191 * public client (optional). 192 */ 193 private RefreshToken existingGrant; 194 195 196 /** 197 * Device secret for native SSO (optional). 198 */ 199 private DeviceSecret deviceSecret; 200 201 202 /** 203 * Custom parameters. 204 */ 205 private final Map<String,List<String>> customParams = new HashMap<>(); 206 207 208 /** 209 * Creates a new builder for a token request with client 210 * authentication. 211 * 212 * @param endpoint The URI of the token endpoint. May be 213 * {@code null} if the {@link #toHTTPRequest} 214 * method is not going to be used. 215 * @param clientAuth The client authentication. Must not be 216 * {@code null}. 217 * @param authzGrant The authorisation grant. Must not be 218 * {@code null}. 219 */ 220 public Builder(final URI endpoint, 221 final ClientAuthentication clientAuth, 222 final AuthorizationGrant authzGrant) { 223 this.endpoint = endpoint; 224 this.clientAuthCandidates = Collections.singletonList(Objects.requireNonNull(clientAuth)); 225 clientID = null; 226 this.authzGrant = Objects.requireNonNull(authzGrant); 227 } 228 229 230 /** 231 * Creates a new builder for a token request with client 232 * authentication candidates. 233 * 234 * @param endpoint The URI of the token endpoint. 235 * May be {@code null} if the 236 * {@link #toHTTPRequest} method is 237 * not going to be used. 238 * @param clientAuthCandidates The client authentication 239 * candidates. Must not be 240 * {@code null}. 241 * @param authzGrant The authorisation grant. Must 242 * not be {@code null}. 243 */ 244 public Builder(final URI endpoint, 245 final List<ClientAuthentication> clientAuthCandidates, 246 final AuthorizationGrant authzGrant) { 247 this.endpoint = endpoint; 248 if (CollectionUtils.isEmpty(clientAuthCandidates)) { 249 throw new IllegalArgumentException("At least one client authentication candidate must be specified"); 250 } 251 this.clientAuthCandidates = Collections.unmodifiableList(clientAuthCandidates); 252 clientID = null; 253 this.authzGrant = Objects.requireNonNull(authzGrant); 254 } 255 256 257 /** 258 * Creates a new builder for a token request with no (explicit) 259 * client authentication. The grant itself may be used to 260 * authenticate the client. 261 * 262 * @param endpoint The URI of the token endpoint. May be 263 * {@code null} if the {@link #toHTTPRequest} 264 * method is not going to be used. 265 * @param clientID The client identifier. Must not be 266 * {@code null}. 267 * @param authzGrant The authorisation grant. Must not be 268 * {@code null}. 269 */ 270 public Builder(final URI endpoint, 271 final ClientID clientID, 272 final AuthorizationGrant authzGrant) { 273 this.endpoint = endpoint; 274 clientAuthCandidates = null; 275 this.clientID = Objects.requireNonNull(clientID); 276 this.authzGrant = Objects.requireNonNull(authzGrant); 277 } 278 279 280 /** 281 * Creates a new builder for a token request with no (explicit) 282 * client authentication, the client identifier is inferred 283 * from the authorisation grant. 284 * 285 * @param endpoint The URI of the token endpoint. May be 286 * {@code null} if the {@link #toHTTPRequest} 287 * method is not going to be used. 288 * @param authzGrant The authorisation grant. Must not be 289 * {@code null}. 290 */ 291 public Builder(final URI endpoint, 292 final AuthorizationGrant authzGrant) { 293 this.endpoint = endpoint; 294 clientAuthCandidates = null; 295 clientID = null; 296 this.authzGrant = Objects.requireNonNull(authzGrant); 297 } 298 299 300 /** 301 * Sets the scope. Corresponds to the optional {@code scope} 302 * parameter. 303 * 304 * @param scope The scope, {@code null} if not specified. 305 * 306 * @return This builder. 307 */ 308 public Builder scope(final Scope scope) { 309 this.scope = scope; 310 return this; 311 } 312 313 314 /** 315 * Sets the Rich Authorisation Request (RAR) details. 316 * Corresponds to the optional {@code authorization_details} 317 * parameter. 318 * 319 * @param authorizationDetails The authorisation details, 320 * {@code null} if not specified. 321 * 322 * @return This builder. 323 */ 324 public Builder authorizationDetails(final List<AuthorizationDetail> authorizationDetails) { 325 this.authorizationDetails = authorizationDetails; 326 return this; 327 } 328 329 330 /** 331 * Sets the resource server URI. Corresponds to the optional 332 * {@code resource} parameter. 333 * 334 * @param resource The resource URI, {@code null} if not 335 * specified. 336 * 337 * @return This builder. 338 */ 339 public Builder resource(final URI resource) { 340 if (resource != null) { 341 this.resources = Collections.singletonList(resource); 342 } else { 343 this.resources = null; 344 } 345 return this; 346 } 347 348 349 /** 350 * Sets the resource server URI(s). Corresponds to the optional 351 * {@code resource} parameter. 352 * 353 * @param resources The resource URI(s), {@code null} if not 354 * specified. 355 * 356 * @return This builder. 357 */ 358 public Builder resources(final URI ... resources) { 359 if (resources != null) { 360 this.resources = Arrays.asList(resources); 361 } else { 362 this.resources = null; 363 } 364 return this; 365 } 366 367 368 /** 369 * Sets the existing refresh token for incremental 370 * authorisation of a public client. Corresponds to the 371 * optional {@code existing_grant} parameter. 372 * 373 * @param existingGrant Existing refresh token for incremental 374 * authorisation of a public client, 375 * {@code null} if not specified. 376 * 377 * @return This builder. 378 */ 379 public Builder existingGrant(final RefreshToken existingGrant) { 380 this.existingGrant = existingGrant; 381 return this; 382 } 383 384 385 /** 386 * Sets the device secret for native SSO. Corresponds to the 387 * optional {@code device_secret} parameter. 388 * 389 * @param deviceSecret The device secret, {@code null} if not 390 * specified. 391 * 392 * @return This builder. 393 */ 394 public Builder deviceSecret(final DeviceSecret deviceSecret) { 395 this.deviceSecret = deviceSecret; 396 return this; 397 } 398 399 400 /** 401 * Sets a custom parameter. 402 * 403 * @param name The parameter name. Must not be {@code null}. 404 * @param values The parameter values, {@code null} if not 405 * specified. 406 * 407 * @return This builder. 408 */ 409 public Builder customParameter(final String name, final String ... values) { 410 if (values == null || values.length == 0) { 411 customParams.remove(name); 412 } else { 413 customParams.put(name, Arrays.asList(values)); 414 } 415 return this; 416 } 417 418 419 /** 420 * Builds a new token request. 421 * 422 * @return The token request. 423 */ 424 public TokenRequest build() { 425 426 try { 427 if (clientAuthCandidates != null) { 428 return new TokenRequest( 429 endpoint, 430 clientAuthCandidates, 431 authzGrant, 432 scope, 433 authorizationDetails, 434 resources, 435 deviceSecret, 436 customParams); 437 } 438 439 return new TokenRequest( 440 endpoint, 441 clientID, 442 authzGrant, 443 scope, 444 authorizationDetails, 445 resources, 446 existingGrant, 447 deviceSecret, 448 customParams); 449 } catch (IllegalArgumentException e) { 450 throw new IllegalStateException(e.getMessage(), e); 451 } 452 } 453 } 454 455 456 /** 457 * Creates a new token request with client authentication. 458 * 459 * @param endpoint The URI of the token endpoint. May be 460 * {@code null} if the {@link #toHTTPRequest} method 461 * is not going to be used. 462 * @param clientAuth The client authentication. Must not be 463 * {@code null}. 464 * @param authzGrant The authorisation grant. Must not be {@code null}. 465 * @param scope The requested scope, {@code null} if not 466 * specified. 467 */ 468 public TokenRequest(final URI endpoint, 469 final ClientAuthentication clientAuth, 470 final AuthorizationGrant authzGrant, 471 final Scope scope) { 472 473 this(endpoint, clientAuth, authzGrant, scope, null, null); 474 } 475 476 477 /** 478 * Creates a new token request with client authentication and extension 479 * and custom parameters. 480 * 481 * @param endpoint The URI of the token endpoint. May be 482 * {@code null} if the {@link #toHTTPRequest} 483 * method is not going to be used. 484 * @param clientAuth The client authentication. Must not be 485 * {@code null}. 486 * @param authzGrant The authorisation grant. Must not be 487 * {@code null}. 488 * @param scope The requested scope, {@code null} if not 489 * specified. 490 * @param resources The resource URI(s), {@code null} if not 491 * specified. 492 * @param customParams Custom parameters to be included in the request 493 * body, empty map or {@code null} if none. 494 */ 495 @Deprecated 496 public TokenRequest(final URI endpoint, 497 final ClientAuthentication clientAuth, 498 final AuthorizationGrant authzGrant, 499 final Scope scope, 500 final List<URI> resources, 501 final Map<String,List<String>> customParams) { 502 503 this(endpoint, clientAuth, authzGrant, scope, null, resources, customParams); 504 } 505 506 507 /** 508 * Creates a new token request with client authentication and extension 509 * and custom parameters. 510 * 511 * @param endpoint The URI of the token endpoint. May be 512 * {@code null} if the 513 * {@link #toHTTPRequest} method is not 514 * going be used. 515 * @param clientAuth The client authentication. Must not be 516 * {@code null}. 517 * @param authzGrant The authorisation grant. Must not be 518 * {@code null}. 519 * @param scope The requested scope, {@code null} if not 520 * specified. 521 * @param authorizationDetails The Rich Authorisation Request (RAR) 522 * details, {@code null} if not specified. 523 * @param resources The resource URI(s), {@code null} if not 524 * specified. 525 * @param customParams Custom parameters to be included in the 526 * request body, empty map or {@code null} 527 * if none. 528 */ 529 @Deprecated 530 public TokenRequest(final URI endpoint, 531 final ClientAuthentication clientAuth, 532 final AuthorizationGrant authzGrant, 533 final Scope scope, 534 final List<AuthorizationDetail> authorizationDetails, 535 final List<URI> resources, 536 final Map<String,List<String>> customParams) { 537 538 this(endpoint, clientAuth, authzGrant, scope, authorizationDetails, resources, null, customParams); 539 } 540 541 542 /** 543 * Creates a new token request with client authentication. 544 * 545 * @param endpoint The URI of the token endpoint. May be 546 * {@code null} if the {@link #toHTTPRequest} method 547 * is not going to be used. 548 * @param clientAuth The client authentication. Must not be 549 * {@code null}. 550 * @param authzGrant The authorisation grant. Must not be {@code null}. 551 */ 552 @Deprecated 553 public TokenRequest(final URI endpoint, 554 final ClientAuthentication clientAuth, 555 final AuthorizationGrant authzGrant) { 556 557 this(endpoint, clientAuth, authzGrant, null); 558 } 559 560 561 /** 562 * Creates a new token request with no (explicit) client 563 * authentication. The grant itself may be used to authenticate the 564 * client. 565 * 566 * @param endpoint The URI of the token endpoint. May be 567 * {@code null} if the {@link #toHTTPRequest} method 568 * is not going to be used. 569 * @param clientID The client identifier, {@code null} if not 570 * specified. 571 * @param authzGrant The authorisation grant. Must not be {@code null}. 572 * @param scope The requested scope, {@code null} if not 573 * specified. 574 */ 575 public TokenRequest(final URI endpoint, 576 final ClientID clientID, 577 final AuthorizationGrant authzGrant, 578 final Scope scope) { 579 580 this(endpoint, clientID, authzGrant, scope, null, null,null); 581 } 582 583 584 /** 585 * Creates a new token request, with no (explicit) client 586 * authentication and extension and custom parameters. The grant itself 587 * may be used to authenticate the client. 588 * 589 * @param endpoint The URI of the token endpoint. May be 590 * {@code null} if the {@link #toHTTPRequest} 591 * method is not going to be used. 592 * @param clientID The client identifier, {@code null} if not 593 * specified. 594 * @param authzGrant The authorisation grant. Must not be 595 * {@code null}. 596 * @param scope The requested scope, {@code null} if not 597 * specified. 598 * @param resources The resource URI(s), {@code null} if not 599 * specified. 600 * @param existingGrant Existing refresh token for incremental 601 * authorisation of a public client, {@code null} 602 * if not specified. 603 * @param customParams Custom parameters to be included in the request 604 * body, empty map or {@code null} if none. 605 */ 606 @Deprecated 607 public TokenRequest(final URI endpoint, 608 final ClientID clientID, 609 final AuthorizationGrant authzGrant, 610 final Scope scope, 611 final List<URI> resources, 612 final RefreshToken existingGrant, 613 final Map<String,List<String>> customParams) { 614 615 this(endpoint, clientID, authzGrant, scope, null, resources, existingGrant, customParams); 616 } 617 618 619 /** 620 * Creates a new token request, with no (explicit) client 621 * authentication and extension and custom parameters. The grant itself 622 * may be used to authenticate the client. 623 * 624 * @param endpoint The URI of the token endpoint. May be 625 * {@code null} if the 626 * {@link #toHTTPRequest} 627 * method is not going to be used. 628 * @param clientID The client identifier, {@code null} if 629 * not specified. 630 * @param authzGrant The authorisation grant. Must not be 631 * {@code null}. 632 * @param scope The requested scope, {@code null} if not 633 * specified. 634 * @param authorizationDetails The Rich Authorisation Request (RAR) 635 * details, {@code null} if not specified. 636 * @param resources The resource URI(s), {@code null} if not 637 * specified. 638 * @param existingGrant Existing refresh token for incremental 639 * authorisation of a public client, 640 * {@code null} if not specified. 641 * @param customParams Custom parameters to be included in the 642 * request body, empty map or {@code null} 643 * if none. 644 */ 645 @Deprecated 646 public TokenRequest(final URI endpoint, 647 final ClientID clientID, 648 final AuthorizationGrant authzGrant, 649 final Scope scope, 650 final List<AuthorizationDetail> authorizationDetails, 651 final List<URI> resources, 652 final RefreshToken existingGrant, 653 final Map<String,List<String>> customParams) { 654 655 this(endpoint, clientID, authzGrant, scope, authorizationDetails, resources, existingGrant, null, customParams); 656 } 657 658 659 /** 660 * Creates a new token request, with no (explicit) client 661 * authentication. The grant itself may be used to authenticate the 662 * client. 663 * 664 * @param endpoint The URI of the token endpoint. May be 665 * {@code null} if the {@link #toHTTPRequest} method 666 * is not going to be used. 667 * @param clientID The client identifier, {@code null} if not 668 * specified. 669 * @param authzGrant The authorisation grant. Must not be {@code null}. 670 */ 671 @Deprecated 672 public TokenRequest(final URI endpoint, 673 final ClientID clientID, 674 final AuthorizationGrant authzGrant) { 675 676 this(endpoint, clientID, authzGrant, null); 677 } 678 679 680 /** 681 * Creates a new token request with no (explicit) client 682 * authentication, the client identifier is inferred from the 683 * authorisation grant. 684 * 685 * @param endpoint The URI of the token endpoint. May be 686 * {@code null} if the {@link #toHTTPRequest} method 687 * is not going to be used. 688 * @param authzGrant The authorisation grant. Must not be {@code null}. 689 * @param scope The requested scope, {@code null} if not 690 * specified. 691 */ 692 public TokenRequest(final URI endpoint, 693 final AuthorizationGrant authzGrant, 694 final Scope scope) { 695 696 this(endpoint, (ClientID)null, authzGrant, scope); 697 } 698 699 700 /** 701 * Creates a new token request with no (explicit) client 702 * authentication, the client identifier is inferred from the 703 * authorisation grant. 704 * 705 * @param endpoint The URI of the token endpoint. May be 706 * {@code null} if the {@link #toHTTPRequest} method 707 * is not going to be used. 708 * @param authzGrant The authorisation grant. Must not be {@code null}. 709 */ 710 @Deprecated 711 public TokenRequest(final URI endpoint, 712 final AuthorizationGrant authzGrant) { 713 714 this(endpoint, (ClientID)null, authzGrant, null); 715 } 716 717 718 /** 719 * Creates a new token request with client authentication and extension 720 * and custom parameters. 721 * 722 * @param endpoint The URI of the token endpoint. May be 723 * {@code null} if the 724 * {@link #toHTTPRequest} method is not 725 * going be used. 726 * @param clientAuth The client authentication. Must not be 727 * {@code null}. 728 * @param authzGrant The authorisation grant. Must not be 729 * {@code null}. 730 * @param scope The requested scope, {@code null} if not 731 * specified. 732 * @param authorizationDetails The Rich Authorisation Request (RAR) 733 * details, {@code null} if not specified. 734 * @param resources The resource URI(s), {@code null} if not 735 * specified. 736 * @param deviceSecret The device secret, {@code null} if not 737 * specified. 738 * @param customParams Custom parameters to be included in the 739 * request body, empty map or {@code null} 740 * if none. 741 */ 742 public TokenRequest(final URI endpoint, 743 final ClientAuthentication clientAuth, 744 final AuthorizationGrant authzGrant, 745 final Scope scope, 746 final List<AuthorizationDetail> authorizationDetails, 747 final List<URI> resources, 748 final DeviceSecret deviceSecret, 749 final Map<String,List<String>> customParams) { 750 751 this(endpoint, Collections.singletonList(clientAuth), authzGrant, scope, authorizationDetails, resources, deviceSecret, customParams); 752 } 753 754 755 /** 756 * Creates a new token request with client authentication candidates 757 * and extension and custom parameters. 758 * 759 * @param endpoint The URI of the token endpoint. May be 760 * {@code null} if the 761 * {@link #toHTTPRequest} method is not 762 * going be used. 763 * @param clientAuthCandidates The client authentication candidates. 764 * Must not be {@code null}. 765 * @param authzGrant The authorisation grant. Must not be 766 * {@code null}. 767 * @param scope The requested scope, {@code null} if not 768 * specified. 769 * @param authorizationDetails The Rich Authorisation Request (RAR) 770 * details, {@code null} if not specified. 771 * @param resources The resource URI(s), {@code null} if not 772 * specified. 773 * @param deviceSecret The device secret, {@code null} if not 774 * specified. 775 * @param customParams Custom parameters to be included in the 776 * request body, empty map or {@code null} 777 * if none. 778 */ 779 public TokenRequest(final URI endpoint, 780 final List<ClientAuthentication> clientAuthCandidates, 781 final AuthorizationGrant authzGrant, 782 final Scope scope, 783 final List<AuthorizationDetail> authorizationDetails, 784 final List<URI> resources, 785 final DeviceSecret deviceSecret, 786 final Map<String,List<String>> customParams) { 787 788 super(endpoint, Objects.requireNonNull(clientAuthCandidates)); 789 for (ClientAuthentication ca: clientAuthCandidates) { 790 Objects.requireNonNull(ca); 791 } 792 793 this.authzGrant = Objects.requireNonNull(authzGrant); 794 795 this.scope = scope; 796 797 if (resources != null) { 798 for (URI resourceURI: resources) { 799 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 800 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 801 } 802 } 803 804 this.authorizationDetails = authorizationDetails; 805 806 this.resources = resources; 807 808 this.existingGrant = null; // only for public client 809 810 this.deviceSecret = deviceSecret; 811 812 if (MapUtils.isNotEmpty(customParams)) { 813 this.customParams = customParams; 814 } else { 815 this.customParams = Collections.emptyMap(); 816 } 817 } 818 819 820 /** 821 * Creates a new token request, with no (explicit) client 822 * authentication and extension and custom parameters. The grant itself 823 * may be used to authenticate the client. 824 * 825 * @param endpoint The URI of the token endpoint. May be 826 * {@code null} if the 827 * {@link #toHTTPRequest} 828 * method is not going to be used. 829 * @param clientID The client identifier, {@code null} if 830 * not specified. 831 * @param authzGrant The authorisation grant. Must not be 832 * {@code null}. 833 * @param scope The requested scope, {@code null} if not 834 * specified. 835 * @param authorizationDetails The Rich Authorisation Request (RAR) 836 * details, {@code null} if not specified. 837 * @param resources The resource URI(s), {@code null} if not 838 * specified. 839 * @param existingGrant Existing refresh token for incremental 840 * authorisation of a public client, 841 * {@code null} if not specified. 842 * @param deviceSecret The device secret, {@code null} if not 843 * specified. 844 * @param customParams Custom parameters to be included in the 845 * request body, empty map or {@code null} 846 * if none. 847 */ 848 public TokenRequest(final URI endpoint, 849 final ClientID clientID, 850 final AuthorizationGrant authzGrant, 851 final Scope scope, 852 final List<AuthorizationDetail> authorizationDetails, 853 final List<URI> resources, 854 final RefreshToken existingGrant, 855 final DeviceSecret deviceSecret, 856 final Map<String,List<String>> customParams) { 857 858 super(endpoint, clientID); 859 860 if (authzGrant.getType().requiresClientAuthentication()) { 861 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication"); 862 } 863 864 if (authzGrant.getType().requiresClientID() && clientID == null) { 865 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter"); 866 } 867 868 this.authzGrant = authzGrant; 869 870 this.scope = scope; 871 872 if (resources != null) { 873 for (URI resourceURI: resources) { 874 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 875 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 876 } 877 } 878 879 this.authorizationDetails = authorizationDetails; 880 881 this.resources = resources; 882 883 this.existingGrant = existingGrant; 884 885 this.deviceSecret = deviceSecret; 886 887 if (MapUtils.isNotEmpty(customParams)) { 888 this.customParams = customParams; 889 } else { 890 this.customParams = Collections.emptyMap(); 891 } 892 } 893 894 895 /** 896 * Returns the authorisation grant. 897 * 898 * @return The authorisation grant. 899 */ 900 public AuthorizationGrant getAuthorizationGrant() { 901 902 return authzGrant; 903 } 904 905 906 /** 907 * Returns the requested scope. Corresponds to the {@code scope} 908 * parameter. 909 * 910 * @return The requested scope, {@code null} if not specified. 911 */ 912 public Scope getScope() { 913 914 return scope; 915 } 916 917 918 /** 919 * Returns the Rich Authorisation Request (RAR) details. Corresponds to 920 * the {@code authorization_details} parameter. 921 * 922 * @return The authorisation details, {@code null} if not specified. 923 */ 924 public List<AuthorizationDetail> getAuthorizationDetails() { 925 926 return authorizationDetails; 927 } 928 929 930 /** 931 * Returns the resource server URI. Corresponds to the {@code resource} 932 * parameter. 933 * 934 * @return The resource URI(s), {@code null} if not specified. 935 */ 936 public List<URI> getResources() { 937 938 return resources; 939 } 940 941 942 /** 943 * Returns the existing refresh token for incremental authorisation of 944 * a public client. Corresponds to the {@code existing_grant} 945 * parameter. 946 * 947 * @return The existing grant, {@code null} if not specified. 948 */ 949 public RefreshToken getExistingGrant() { 950 951 return existingGrant; 952 } 953 954 955 /** 956 * Returns the device secret for native SSO. Corresponds to the 957 * {@code device_secret} parameter. 958 * 959 * @return The device secret, {@code null} if not specified. 960 */ 961 public DeviceSecret getDeviceSecret() { 962 963 return deviceSecret; 964 } 965 966 967 /** 968 * Returns the additional custom parameters included in the request 969 * body. 970 * 971 * @return The additional custom parameters as an unmodifiable map, 972 * empty map if none. 973 */ 974 public Map<String,List<String>> getCustomParameters () { 975 976 return Collections.unmodifiableMap(customParams); 977 } 978 979 980 /** 981 * Returns the specified custom parameter included in the request body. 982 * 983 * @param name The parameter name. Must not be {@code null}. 984 * 985 * @return The parameter value(s), {@code null} if not specified. 986 */ 987 public List<String> getCustomParameter(final String name) { 988 989 return customParams.get(name); 990 } 991 992 993 @Override 994 public HTTPRequest toHTTPRequest() { 995 996 if (getEndpointURI() == null) 997 throw new SerializeException("The endpoint URI is not specified"); 998 999 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 1000 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 1001 1002 if (getClientAuthentication() != null) { 1003 for (ClientAuthentication ca: getClientAuthenticationCandidates()) { 1004 ca.applyTo(httpRequest); 1005 } 1006 } 1007 1008 Map<String, List<String>> params; 1009 try { 1010 params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters()); 1011 } catch (ParseException e) { 1012 throw new SerializeException(e.getMessage(), e); 1013 } 1014 params.putAll(getAuthorizationGrant().toParameters()); 1015 1016 switch (getAuthorizationGrant().getType().getScopeRequirementInTokenRequest()) { 1017 case REQUIRED: 1018 if (CollectionUtils.isEmpty(getScope())) { 1019 throw new SerializeException("Scope is required for the " + getAuthorizationGrant().getType() + " grant"); 1020 } 1021 params.put("scope", Collections.singletonList(getScope().toString())); 1022 break; 1023 case OPTIONAL: 1024 if (CollectionUtils.isNotEmpty(getScope())) { 1025 params.put("scope", Collections.singletonList(getScope().toString())); 1026 } 1027 break; 1028 case NOT_ALLOWED: 1029 default: 1030 break; 1031 } 1032 1033 if (getClientID() != null) { 1034 params.put("client_id", Collections.singletonList(getClientID().getValue())); 1035 } 1036 1037 if (getAuthorizationDetails() != null) { 1038 params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails()))); 1039 } 1040 1041 if (getResources() != null) { 1042 List<String> values = new LinkedList<>(); 1043 for (URI uri: getResources()) { 1044 if (uri == null) 1045 continue; 1046 values.add(uri.toString()); 1047 } 1048 params.put("resource", values); 1049 } 1050 1051 if (getExistingGrant() != null) { 1052 params.put("existing_grant", Collections.singletonList(getExistingGrant().getValue())); 1053 } 1054 1055 if (getDeviceSecret() != null) { 1056 params.put("device_secret", Collections.singletonList(getDeviceSecret().getValue())); 1057 } 1058 1059 if (! getCustomParameters().isEmpty()) { 1060 params.putAll(getCustomParameters()); 1061 } 1062 1063 httpRequest.setBody(URLUtils.serializeParameters(params)); 1064 1065 return httpRequest; 1066 } 1067 1068 1069 /** 1070 * Parses a token request from the specified HTTP request. 1071 * 1072 * @param httpRequest The HTTP request. Must not be {@code null}. 1073 * 1074 * @return The token request. 1075 * 1076 * @throws ParseException If the HTTP request couldn't be parsed to a 1077 * token request. 1078 */ 1079 public static TokenRequest parse(final HTTPRequest httpRequest) 1080 throws ParseException { 1081 1082 // Only HTTP POST accepted 1083 URI endpoint = httpRequest.getURI(); 1084 httpRequest.ensureMethod(HTTPRequest.Method.POST); 1085 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 1086 1087 // Parse client authentication candidates, if any 1088 List<ClientAuthentication> clientAuthCandidates; 1089 try { 1090 clientAuthCandidates = ClientAuthentication.parseCandidates(httpRequest); 1091 } catch (ParseException e) { 1092 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 1093 } 1094 1095 // No fragment! May use query component! 1096 Map<String,List<String>> params = httpRequest.getBodyAsFormParameters(); 1097 1098 Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS); 1099 if (! repeatParams.isEmpty()) { 1100 String msg = "Parameter(s) present more than once: " + repeatParams; 1101 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg)); 1102 } 1103 1104 // Parse grant 1105 AuthorizationGrant grant = AuthorizationGrant.parse(params); 1106 1107 if (CollectionUtils.isEmpty(clientAuthCandidates) && grant.getType().requiresClientAuthentication()) { 1108 String msg = "Missing client authentication"; 1109 throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg)); 1110 } 1111 1112 // Parse client id 1113 ClientID clientID = null; 1114 1115 if (CollectionUtils.isEmpty(clientAuthCandidates)) { 1116 1117 // Parse optional client ID 1118 String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id"); 1119 1120 if (StringUtils.isNotBlank(clientIDString)) 1121 clientID = new ClientID(clientIDString); 1122 1123 if (clientID == null && grant.getType().requiresClientID()) { 1124 String msg = "Missing required client_id parameter"; 1125 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1126 } 1127 } 1128 1129 // Parse optional scope 1130 String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope"); 1131 1132 ParameterRequirement scopeRequirement = grant.getType().getScopeRequirementInTokenRequest(); 1133 1134 Scope scope = null; 1135 1136 if (scopeValue != null && (ParameterRequirement.REQUIRED.equals(scopeRequirement) || ParameterRequirement.OPTIONAL.equals(scopeRequirement))) { 1137 scope = Scope.parse(scopeValue); 1138 } 1139 1140 // Parse optional RAR 1141 String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details"); 1142 1143 List<AuthorizationDetail> authorizationDetails = null; 1144 1145 if (json != null) { 1146 authorizationDetails = AuthorizationDetail.parseList(json); 1147 } 1148 1149 // Parse optional resource URIs 1150 List<URI> resources = null; 1151 1152 List<String> vList = params.get("resource"); 1153 1154 if (vList != null) { 1155 1156 resources = new LinkedList<>(); 1157 1158 for (String uriValue: vList) { 1159 1160 if (uriValue == null) 1161 continue; 1162 1163 String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue; 1164 1165 URI resourceURI; 1166 try { 1167 resourceURI = new URI(uriValue); 1168 } catch (URISyntaxException e) { 1169 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 1170 } 1171 1172 if (! ResourceUtils.isLegalResourceURI(resourceURI)) { 1173 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 1174 } 1175 1176 resources.add(resourceURI); 1177 } 1178 } 1179 1180 String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant"); 1181 RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null; 1182 1183 DeviceSecret deviceSecret = DeviceSecret.parse(MultivaluedMapUtils.getFirstValue(params, "device_secret")); 1184 1185 // Parse custom parameters 1186 Map<String,List<String>> customParams = new HashMap<>(); 1187 1188 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1189 1190 if (REGISTERED_PARAMETER_NAMES.contains(p.getKey().toLowerCase())) { 1191 continue; // skip 1192 } 1193 1194 if (! grant.getType().getRequestParameterNames().contains(p.getKey())) { 1195 // We have a custom (non-registered) parameter 1196 customParams.put(p.getKey(), p.getValue()); 1197 } 1198 } 1199 1200 if (CollectionUtils.isNotEmpty(clientAuthCandidates)) { 1201 return new TokenRequest(endpoint, clientAuthCandidates, grant, scope, authorizationDetails, resources, deviceSecret, customParams); 1202 } else { 1203 // public client 1204 return new TokenRequest(endpoint, clientID, grant, scope, authorizationDetails, resources, existingGrant, deviceSecret, customParams); 1205 } 1206 } 1207}