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, {@code null} if none. 156 */ 157 private final ClientAuthentication clientAuth; 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.clientAuth = 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 no (explicit) 233 * client authentication. The grant itself may be used to 234 * authenticate the client. 235 * 236 * @param endpoint The URI of the token endpoint. May be 237 * {@code null} if the {@link #toHTTPRequest} 238 * method is not going to be used. 239 * @param clientID The client identifier. Must not be 240 * {@code null}. 241 * @param authzGrant The authorisation grant. Must not be 242 * {@code null}. 243 */ 244 public Builder(final URI endpoint, 245 final ClientID clientID, 246 final AuthorizationGrant authzGrant) { 247 this.endpoint = endpoint; 248 clientAuth = null; 249 this.clientID = Objects.requireNonNull(clientID); 250 this.authzGrant = Objects.requireNonNull(authzGrant); 251 } 252 253 254 /** 255 * Creates a new builder for a token request with no (explicit) 256 * client authentication, the client identifier is inferred 257 * from the authorisation grant. 258 * 259 * @param endpoint The URI of the token endpoint. May be 260 * {@code null} if the {@link #toHTTPRequest} 261 * method is not going to be used. 262 * @param authzGrant The authorisation grant. Must not be 263 * {@code null}. 264 */ 265 public Builder(final URI endpoint, 266 final AuthorizationGrant authzGrant) { 267 this.endpoint = endpoint; 268 clientAuth = null; 269 clientID = null; 270 this.authzGrant = Objects.requireNonNull(authzGrant); 271 } 272 273 274 /** 275 * Sets the scope. Corresponds to the optional {@code scope} 276 * parameter. 277 * 278 * @param scope The scope, {@code null} if not specified. 279 * 280 * @return This builder. 281 */ 282 public Builder scope(final Scope scope) { 283 this.scope = scope; 284 return this; 285 } 286 287 288 /** 289 * Sets the Rich Authorisation Request (RAR) details. 290 * Corresponds to the optional {@code authorization_details} 291 * parameter. 292 * 293 * @param authorizationDetails The authorisation details, 294 * {@code null} if not specified. 295 * 296 * @return This builder. 297 */ 298 public Builder authorizationDetails(final List<AuthorizationDetail> authorizationDetails) { 299 this.authorizationDetails = authorizationDetails; 300 return this; 301 } 302 303 304 /** 305 * Sets the resource server URI. Corresponds to the optional 306 * {@code resource} parameter. 307 * 308 * @param resource The resource URI, {@code null} if not 309 * specified. 310 * 311 * @return This builder. 312 */ 313 public Builder resource(final URI resource) { 314 if (resource != null) { 315 this.resources = Collections.singletonList(resource); 316 } else { 317 this.resources = null; 318 } 319 return this; 320 } 321 322 323 /** 324 * Sets the resource server URI(s). Corresponds to the optional 325 * {@code resource} parameter. 326 * 327 * @param resources The resource URI(s), {@code null} if not 328 * specified. 329 * 330 * @return This builder. 331 */ 332 public Builder resources(final URI ... resources) { 333 if (resources != null) { 334 this.resources = Arrays.asList(resources); 335 } else { 336 this.resources = null; 337 } 338 return this; 339 } 340 341 342 /** 343 * Sets the existing refresh token for incremental 344 * authorisation of a public client. Corresponds to the 345 * optional {@code existing_grant} parameter. 346 * 347 * @param existingGrant Existing refresh token for incremental 348 * authorisation of a public client, 349 * {@code null} if not specified. 350 * 351 * @return This builder. 352 */ 353 public Builder existingGrant(final RefreshToken existingGrant) { 354 this.existingGrant = existingGrant; 355 return this; 356 } 357 358 359 /** 360 * Sets the device secret for native SSO. Corresponds to the 361 * optional {@code device_secret} parameter. 362 * 363 * @param deviceSecret The device secret, {@code null} if not 364 * specified. 365 * 366 * @return This builder. 367 */ 368 public Builder deviceSecret(final DeviceSecret deviceSecret) { 369 this.deviceSecret = deviceSecret; 370 return this; 371 } 372 373 374 /** 375 * Sets a custom parameter. 376 * 377 * @param name The parameter name. Must not be {@code null}. 378 * @param values The parameter values, {@code null} if not 379 * specified. 380 * 381 * @return This builder. 382 */ 383 public Builder customParameter(final String name, final String ... values) { 384 if (values == null || values.length == 0) { 385 customParams.remove(name); 386 } else { 387 customParams.put(name, Arrays.asList(values)); 388 } 389 return this; 390 } 391 392 393 /** 394 * Builds a new token request. 395 * 396 * @return The token request. 397 */ 398 public TokenRequest build() { 399 400 try { 401 if (clientAuth != null) { 402 return new TokenRequest( 403 endpoint, 404 clientAuth, 405 authzGrant, 406 scope, 407 authorizationDetails, 408 resources, 409 deviceSecret, 410 customParams); 411 } 412 413 return new TokenRequest( 414 endpoint, 415 clientID, 416 authzGrant, 417 scope, 418 authorizationDetails, 419 resources, 420 existingGrant, 421 deviceSecret, 422 customParams); 423 } catch (IllegalArgumentException e) { 424 throw new IllegalStateException(e.getMessage(), e); 425 } 426 } 427 } 428 429 430 /** 431 * Creates a new token request with client authentication. 432 * 433 * @param endpoint The URI of the token endpoint. May be 434 * {@code null} if the {@link #toHTTPRequest} method 435 * is not going to be used. 436 * @param clientAuth The client authentication. Must not be 437 * {@code null}. 438 * @param authzGrant The authorisation grant. Must not be {@code null}. 439 * @param scope The requested scope, {@code null} if not 440 * specified. 441 */ 442 public TokenRequest(final URI endpoint, 443 final ClientAuthentication clientAuth, 444 final AuthorizationGrant authzGrant, 445 final Scope scope) { 446 447 this(endpoint, clientAuth, authzGrant, scope, null, null); 448 } 449 450 451 /** 452 * Creates a new token request with client authentication and extension 453 * and custom parameters. 454 * 455 * @param endpoint The URI of the token endpoint. May be 456 * {@code null} if the {@link #toHTTPRequest} 457 * method is not going to be used. 458 * @param clientAuth The client authentication. Must not be 459 * {@code null}. 460 * @param authzGrant The authorisation grant. Must not be 461 * {@code null}. 462 * @param scope The requested scope, {@code null} if not 463 * specified. 464 * @param resources The resource URI(s), {@code null} if not 465 * specified. 466 * @param customParams Custom parameters to be included in the request 467 * body, empty map or {@code null} if none. 468 */ 469 @Deprecated 470 public TokenRequest(final URI endpoint, 471 final ClientAuthentication clientAuth, 472 final AuthorizationGrant authzGrant, 473 final Scope scope, 474 final List<URI> resources, 475 final Map<String,List<String>> customParams) { 476 477 this(endpoint, clientAuth, authzGrant, scope, null, resources, customParams); 478 } 479 480 481 /** 482 * Creates a new token request with client authentication and extension 483 * and custom parameters. 484 * 485 * @param endpoint The URI of the token endpoint. May be 486 * {@code null} if the 487 * {@link #toHTTPRequest} method is not 488 * going be used. 489 * @param clientAuth The client authentication. Must not be 490 * {@code null}. 491 * @param authzGrant The authorisation grant. Must not be 492 * {@code null}. 493 * @param scope The requested scope, {@code null} if not 494 * specified. 495 * @param authorizationDetails The Rich Authorisation Request (RAR) 496 * details, {@code null} if not specified. 497 * @param resources The resource URI(s), {@code null} if not 498 * specified. 499 * @param customParams Custom parameters to be included in the 500 * request body, empty map or {@code null} 501 * if none. 502 */ 503 @Deprecated 504 public TokenRequest(final URI endpoint, 505 final ClientAuthentication clientAuth, 506 final AuthorizationGrant authzGrant, 507 final Scope scope, 508 final List<AuthorizationDetail> authorizationDetails, 509 final List<URI> resources, 510 final Map<String,List<String>> customParams) { 511 512 this(endpoint, clientAuth, authzGrant, scope, authorizationDetails, resources, null, customParams); 513 } 514 515 516 /** 517 * Creates a new token request with client authentication. 518 * 519 * @param endpoint The URI of the token endpoint. May be 520 * {@code null} if the {@link #toHTTPRequest} method 521 * is not going to be used. 522 * @param clientAuth The client authentication. Must not be 523 * {@code null}. 524 * @param authzGrant The authorisation grant. Must not be {@code null}. 525 */ 526 @Deprecated 527 public TokenRequest(final URI endpoint, 528 final ClientAuthentication clientAuth, 529 final AuthorizationGrant authzGrant) { 530 531 this(endpoint, clientAuth, authzGrant, null); 532 } 533 534 535 /** 536 * Creates a new token request with no (explicit) client 537 * authentication. The grant itself may be used to authenticate the 538 * client. 539 * 540 * @param endpoint The URI of the token endpoint. May be 541 * {@code null} if the {@link #toHTTPRequest} method 542 * is not going to be used. 543 * @param clientID The client identifier, {@code null} if not 544 * specified. 545 * @param authzGrant The authorisation grant. Must not be {@code null}. 546 * @param scope The requested scope, {@code null} if not 547 * specified. 548 */ 549 public TokenRequest(final URI endpoint, 550 final ClientID clientID, 551 final AuthorizationGrant authzGrant, 552 final Scope scope) { 553 554 this(endpoint, clientID, authzGrant, scope, null, null,null); 555 } 556 557 558 /** 559 * Creates a new token request, with no (explicit) client 560 * authentication and extension and custom parameters. The grant itself 561 * may be used to authenticate the client. 562 * 563 * @param endpoint The URI of the token endpoint. May be 564 * {@code null} if the {@link #toHTTPRequest} 565 * method is not going to be used. 566 * @param clientID The client identifier, {@code null} if not 567 * specified. 568 * @param authzGrant The authorisation grant. Must not be 569 * {@code null}. 570 * @param scope The requested scope, {@code null} if not 571 * specified. 572 * @param resources The resource URI(s), {@code null} if not 573 * specified. 574 * @param existingGrant Existing refresh token for incremental 575 * authorisation of a public client, {@code null} 576 * if not specified. 577 * @param customParams Custom parameters to be included in the request 578 * body, empty map or {@code null} if none. 579 */ 580 @Deprecated 581 public TokenRequest(final URI endpoint, 582 final ClientID clientID, 583 final AuthorizationGrant authzGrant, 584 final Scope scope, 585 final List<URI> resources, 586 final RefreshToken existingGrant, 587 final Map<String,List<String>> customParams) { 588 589 this(endpoint, clientID, authzGrant, scope, null, resources, existingGrant, customParams); 590 } 591 592 593 /** 594 * Creates a new token request, with no (explicit) client 595 * authentication and extension and custom parameters. The grant itself 596 * may be used to authenticate the client. 597 * 598 * @param endpoint The URI of the token endpoint. May be 599 * {@code null} if the 600 * {@link #toHTTPRequest} 601 * method is not going to be used. 602 * @param clientID The client identifier, {@code null} if 603 * not specified. 604 * @param authzGrant The authorisation grant. Must not be 605 * {@code null}. 606 * @param scope The requested scope, {@code null} if not 607 * specified. 608 * @param authorizationDetails The Rich Authorisation Request (RAR) 609 * details, {@code null} if not specified. 610 * @param resources The resource URI(s), {@code null} if not 611 * specified. 612 * @param existingGrant Existing refresh token for incremental 613 * authorisation of a public client, 614 * {@code null} if not specified. 615 * @param customParams Custom parameters to be included in the 616 * request body, empty map or {@code null} 617 * if none. 618 */ 619 @Deprecated 620 public TokenRequest(final URI endpoint, 621 final ClientID clientID, 622 final AuthorizationGrant authzGrant, 623 final Scope scope, 624 final List<AuthorizationDetail> authorizationDetails, 625 final List<URI> resources, 626 final RefreshToken existingGrant, 627 final Map<String,List<String>> customParams) { 628 629 this(endpoint, clientID, authzGrant, scope, authorizationDetails, resources, existingGrant, null, customParams); 630 } 631 632 633 /** 634 * Creates a new token request, with no (explicit) client 635 * authentication. The grant itself may be used to authenticate the 636 * client. 637 * 638 * @param endpoint The URI of the token endpoint. May be 639 * {@code null} if the {@link #toHTTPRequest} method 640 * is not going to be used. 641 * @param clientID The client identifier, {@code null} if not 642 * specified. 643 * @param authzGrant The authorisation grant. Must not be {@code null}. 644 */ 645 @Deprecated 646 public TokenRequest(final URI endpoint, 647 final ClientID clientID, 648 final AuthorizationGrant authzGrant) { 649 650 this(endpoint, clientID, authzGrant, null); 651 } 652 653 654 /** 655 * Creates a new token request with no (explicit) client 656 * authentication, the client identifier is inferred from the 657 * authorisation grant. 658 * 659 * @param endpoint The URI of the token endpoint. May be 660 * {@code null} if the {@link #toHTTPRequest} method 661 * is not going to be used. 662 * @param authzGrant The authorisation grant. Must not be {@code null}. 663 * @param scope The requested scope, {@code null} if not 664 * specified. 665 */ 666 public TokenRequest(final URI endpoint, 667 final AuthorizationGrant authzGrant, 668 final Scope scope) { 669 670 this(endpoint, (ClientID)null, authzGrant, scope); 671 } 672 673 674 /** 675 * Creates a new token request with no (explicit) client 676 * authentication, the client identifier is inferred from the 677 * authorisation grant. 678 * 679 * @param endpoint The URI of the token endpoint. May be 680 * {@code null} if the {@link #toHTTPRequest} method 681 * is not going to be used. 682 * @param authzGrant The authorisation grant. Must not be {@code null}. 683 */ 684 @Deprecated 685 public TokenRequest(final URI endpoint, 686 final AuthorizationGrant authzGrant) { 687 688 this(endpoint, (ClientID)null, authzGrant, null); 689 } 690 691 692 /** 693 * Creates a new token request with client authentication and extension 694 * and custom parameters. 695 * 696 * @param endpoint The URI of the token endpoint. May be 697 * {@code null} if the 698 * {@link #toHTTPRequest} method is not 699 * going be used. 700 * @param clientAuth The client authentication. Must not be 701 * {@code null}. 702 * @param authzGrant The authorisation grant. Must not be 703 * {@code null}. 704 * @param scope The requested scope, {@code null} if not 705 * specified. 706 * @param authorizationDetails The Rich Authorisation Request (RAR) 707 * details, {@code null} if not specified. 708 * @param resources The resource URI(s), {@code null} if not 709 * specified. 710 * @param deviceSecret The device secret, {@code null} if not 711 * specified. 712 * @param customParams Custom parameters to be included in the 713 * request body, empty map or {@code null} 714 * if none. 715 */ 716 public TokenRequest(final URI endpoint, 717 final ClientAuthentication clientAuth, 718 final AuthorizationGrant authzGrant, 719 final Scope scope, 720 final List<AuthorizationDetail> authorizationDetails, 721 final List<URI> resources, 722 final DeviceSecret deviceSecret, 723 final Map<String,List<String>> customParams) { 724 725 super(endpoint, Objects.requireNonNull(clientAuth)); 726 727 this.authzGrant = Objects.requireNonNull(authzGrant); 728 729 this.scope = scope; 730 731 if (resources != null) { 732 for (URI resourceURI: resources) { 733 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 734 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 735 } 736 } 737 738 this.authorizationDetails = authorizationDetails; 739 740 this.resources = resources; 741 742 this.existingGrant = null; // only for public client 743 744 this.deviceSecret = deviceSecret; 745 746 if (MapUtils.isNotEmpty(customParams)) { 747 this.customParams = customParams; 748 } else { 749 this.customParams = Collections.emptyMap(); 750 } 751 } 752 753 754 /** 755 * Creates a new token request, with no (explicit) client 756 * authentication and extension and custom parameters. The grant itself 757 * may be used to authenticate the client. 758 * 759 * @param endpoint The URI of the token endpoint. May be 760 * {@code null} if the 761 * {@link #toHTTPRequest} 762 * method is not going to be used. 763 * @param clientID The client identifier, {@code null} if 764 * not specified. 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 existingGrant Existing refresh token for incremental 774 * authorisation of a public client, 775 * {@code null} if not specified. 776 * @param deviceSecret The device secret, {@code null} if not 777 * specified. 778 * @param customParams Custom parameters to be included in the 779 * request body, empty map or {@code null} 780 * if none. 781 */ 782 public TokenRequest(final URI endpoint, 783 final ClientID clientID, 784 final AuthorizationGrant authzGrant, 785 final Scope scope, 786 final List<AuthorizationDetail> authorizationDetails, 787 final List<URI> resources, 788 final RefreshToken existingGrant, 789 final DeviceSecret deviceSecret, 790 final Map<String,List<String>> customParams) { 791 792 super(endpoint, clientID); 793 794 if (authzGrant.getType().requiresClientAuthentication()) { 795 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication"); 796 } 797 798 if (authzGrant.getType().requiresClientID() && clientID == null) { 799 throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter"); 800 } 801 802 this.authzGrant = authzGrant; 803 804 this.scope = scope; 805 806 if (resources != null) { 807 for (URI resourceURI: resources) { 808 if (! ResourceUtils.isLegalResourceURI(resourceURI)) 809 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 810 } 811 } 812 813 this.authorizationDetails = authorizationDetails; 814 815 this.resources = resources; 816 817 this.existingGrant = existingGrant; 818 819 this.deviceSecret = deviceSecret; 820 821 if (MapUtils.isNotEmpty(customParams)) { 822 this.customParams = customParams; 823 } else { 824 this.customParams = Collections.emptyMap(); 825 } 826 } 827 828 829 /** 830 * Returns the authorisation grant. 831 * 832 * @return The authorisation grant. 833 */ 834 public AuthorizationGrant getAuthorizationGrant() { 835 836 return authzGrant; 837 } 838 839 840 /** 841 * Returns the requested scope. Corresponds to the {@code scope} 842 * parameter. 843 * 844 * @return The requested scope, {@code null} if not specified. 845 */ 846 public Scope getScope() { 847 848 return scope; 849 } 850 851 852 /** 853 * Returns the Rich Authorisation Request (RAR) details. Corresponds to 854 * the {@code authorization_details} parameter. 855 * 856 * @return The authorisation details, {@code null} if not specified. 857 */ 858 public List<AuthorizationDetail> getAuthorizationDetails() { 859 860 return authorizationDetails; 861 } 862 863 864 /** 865 * Returns the resource server URI. Corresponds to the {@code resource} 866 * parameter. 867 * 868 * @return The resource URI(s), {@code null} if not specified. 869 */ 870 public List<URI> getResources() { 871 872 return resources; 873 } 874 875 876 /** 877 * Returns the existing refresh token for incremental authorisation of 878 * a public client. Corresponds to the {@code existing_grant} 879 * parameter. 880 * 881 * @return The existing grant, {@code null} if not specified. 882 */ 883 public RefreshToken getExistingGrant() { 884 885 return existingGrant; 886 } 887 888 889 /** 890 * Returns the device secret for native SSO. Corresponds to the 891 * {@code device_secret} parameter. 892 * 893 * @return The device secret, {@code null} if not specified. 894 */ 895 public DeviceSecret getDeviceSecret() { 896 897 return deviceSecret; 898 } 899 900 901 /** 902 * Returns the additional custom parameters included in the request 903 * body. 904 * 905 * @return The additional custom parameters as an unmodifiable map, 906 * empty map if none. 907 */ 908 public Map<String,List<String>> getCustomParameters () { 909 910 return Collections.unmodifiableMap(customParams); 911 } 912 913 914 /** 915 * Returns the specified custom parameter included in the request body. 916 * 917 * @param name The parameter name. Must not be {@code null}. 918 * 919 * @return The parameter value(s), {@code null} if not specified. 920 */ 921 public List<String> getCustomParameter(final String name) { 922 923 return customParams.get(name); 924 } 925 926 927 @Override 928 public HTTPRequest toHTTPRequest() { 929 930 if (getEndpointURI() == null) 931 throw new SerializeException("The endpoint URI is not specified"); 932 933 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 934 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 935 936 if (getClientAuthentication() != null) { 937 getClientAuthentication().applyTo(httpRequest); 938 } 939 940 Map<String, List<String>> params; 941 try { 942 params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters()); 943 } catch (ParseException e) { 944 throw new SerializeException(e.getMessage(), e); 945 } 946 params.putAll(getAuthorizationGrant().toParameters()); 947 948 switch (getAuthorizationGrant().getType().getScopeRequirementInTokenRequest()) { 949 case REQUIRED: 950 if (CollectionUtils.isEmpty(getScope())) { 951 throw new SerializeException("Scope is required for the " + getAuthorizationGrant().getType() + " grant"); 952 } 953 params.put("scope", Collections.singletonList(getScope().toString())); 954 break; 955 case OPTIONAL: 956 if (CollectionUtils.isNotEmpty(getScope())) { 957 params.put("scope", Collections.singletonList(getScope().toString())); 958 } 959 break; 960 case NOT_ALLOWED: 961 default: 962 break; 963 } 964 965 if (getClientID() != null) { 966 params.put("client_id", Collections.singletonList(getClientID().getValue())); 967 } 968 969 if (getAuthorizationDetails() != null) { 970 params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails()))); 971 } 972 973 if (getResources() != null) { 974 List<String> values = new LinkedList<>(); 975 for (URI uri: getResources()) { 976 if (uri == null) 977 continue; 978 values.add(uri.toString()); 979 } 980 params.put("resource", values); 981 } 982 983 if (getExistingGrant() != null) { 984 params.put("existing_grant", Collections.singletonList(getExistingGrant().getValue())); 985 } 986 987 if (getDeviceSecret() != null) { 988 params.put("device_secret", Collections.singletonList(getDeviceSecret().getValue())); 989 } 990 991 if (! getCustomParameters().isEmpty()) { 992 params.putAll(getCustomParameters()); 993 } 994 995 httpRequest.setBody(URLUtils.serializeParameters(params)); 996 997 return httpRequest; 998 } 999 1000 1001 /** 1002 * Parses a token request from the specified HTTP request. 1003 * 1004 * @param httpRequest The HTTP request. Must not be {@code null}. 1005 * 1006 * @return The token request. 1007 * 1008 * @throws ParseException If the HTTP request couldn't be parsed to a 1009 * token request. 1010 */ 1011 public static TokenRequest parse(final HTTPRequest httpRequest) 1012 throws ParseException { 1013 1014 // Only HTTP POST accepted 1015 URI endpoint = httpRequest.getURI(); 1016 httpRequest.ensureMethod(HTTPRequest.Method.POST); 1017 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 1018 1019 // Parse client authentication, if any 1020 ClientAuthentication clientAuth; 1021 try { 1022 clientAuth = ClientAuthentication.parse(httpRequest); 1023 } catch (ParseException e) { 1024 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage())); 1025 } 1026 1027 // No fragment! May use query component! 1028 Map<String,List<String>> params = httpRequest.getBodyAsFormParameters(); 1029 1030 Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS); 1031 if (! repeatParams.isEmpty()) { 1032 String msg = "Parameter(s) present more than once: " + repeatParams; 1033 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg)); 1034 } 1035 1036 // Multiple conflicting client auth methods (issue #203)? 1037 if (clientAuth instanceof ClientSecretBasic) { 1038 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 1039 String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion"; 1040 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1041 } 1042 } 1043 1044 // Parse grant 1045 AuthorizationGrant grant = AuthorizationGrant.parse(params); 1046 1047 if (clientAuth == null && grant.getType().requiresClientAuthentication()) { 1048 String msg = "Missing client authentication"; 1049 throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg)); 1050 } 1051 1052 // Parse client id 1053 ClientID clientID = null; 1054 1055 if (clientAuth == null) { 1056 1057 // Parse optional client ID 1058 String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id"); 1059 1060 if (StringUtils.isNotBlank(clientIDString)) 1061 clientID = new ClientID(clientIDString); 1062 1063 if (clientID == null && grant.getType().requiresClientID()) { 1064 String msg = "Missing required client_id parameter"; 1065 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1066 } 1067 } 1068 1069 // Parse optional scope 1070 String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope"); 1071 1072 ParameterRequirement scopeRequirement = grant.getType().getScopeRequirementInTokenRequest(); 1073 1074 Scope scope = null; 1075 1076 if (scopeValue != null && (ParameterRequirement.REQUIRED.equals(scopeRequirement) || ParameterRequirement.OPTIONAL.equals(scopeRequirement))) { 1077 scope = Scope.parse(scopeValue); 1078 } 1079 1080 // Parse optional RAR 1081 String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details"); 1082 1083 List<AuthorizationDetail> authorizationDetails = null; 1084 1085 if (json != null) { 1086 authorizationDetails = AuthorizationDetail.parseList(json); 1087 } 1088 1089 // Parse optional resource URIs 1090 List<URI> resources = null; 1091 1092 List<String> vList = params.get("resource"); 1093 1094 if (vList != null) { 1095 1096 resources = new LinkedList<>(); 1097 1098 for (String uriValue: vList) { 1099 1100 if (uriValue == null) 1101 continue; 1102 1103 String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue; 1104 1105 URI resourceURI; 1106 try { 1107 resourceURI = new URI(uriValue); 1108 } catch (URISyntaxException e) { 1109 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 1110 } 1111 1112 if (! ResourceUtils.isLegalResourceURI(resourceURI)) { 1113 throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg)); 1114 } 1115 1116 resources.add(resourceURI); 1117 } 1118 } 1119 1120 String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant"); 1121 RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null; 1122 1123 DeviceSecret deviceSecret = DeviceSecret.parse(MultivaluedMapUtils.getFirstValue(params, "device_secret")); 1124 1125 // Parse custom parameters 1126 Map<String,List<String>> customParams = new HashMap<>(); 1127 1128 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1129 1130 if (REGISTERED_PARAMETER_NAMES.contains(p.getKey().toLowerCase())) { 1131 continue; // skip 1132 } 1133 1134 if (! grant.getType().getRequestParameterNames().contains(p.getKey())) { 1135 // We have a custom (non-registered) parameter 1136 customParams.put(p.getKey(), p.getValue()); 1137 } 1138 } 1139 1140 if (clientAuth != null) { 1141 return new TokenRequest(endpoint, clientAuth, grant, scope, authorizationDetails, resources, deviceSecret, customParams); 1142 } else { 1143 // public client 1144 return new TokenRequest(endpoint, clientID, grant, scope, authorizationDetails, resources, existingGrant, deviceSecret, customParams); 1145 } 1146 } 1147}