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