001package com.nimbusds.oauth2.sdk.client; 002 003 004import java.net.URI; 005import java.net.URISyntaxException; 006import java.util.*; 007import javax.mail.internet.AddressException; 008import javax.mail.internet.InternetAddress; 009 010import com.nimbusds.jose.JWSAlgorithm; 011import com.nimbusds.jose.jwk.JWKSet; 012import com.nimbusds.langtag.LangTag; 013import com.nimbusds.langtag.LangTagUtils; 014import com.nimbusds.oauth2.sdk.GrantType; 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.ResponseType; 017import com.nimbusds.oauth2.sdk.Scope; 018import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod; 019import com.nimbusds.oauth2.sdk.id.SoftwareID; 020import com.nimbusds.oauth2.sdk.id.SoftwareVersion; 021import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 022import net.minidev.json.JSONArray; 023import net.minidev.json.JSONObject; 024 025 026/** 027 * Client metadata. 028 * 029 * <p>Example client metadata, serialised to a JSON object: 030 * 031 * <pre> 032 * { 033 * "redirect_uris" : ["https://client.example.org/callback", 034 * "https://client.example.org/callback2"], 035 * "client_name" : "My Example Client", 036 * "client_name#ja-Jpan-JP" : "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D", 037 * "token_endpoint_auth_method" : "client_secret_basic", 038 * "scope" : "read write dolphin", 039 * "logo_uri" : "https://client.example.org/logo.png", 040 * "jwks_uri" : "https://client.example.org/my_public_keys.jwks" 041 * } 042 * </pre> 043 * 044 * <p>Related specifications: 045 * 046 * <ul> 047 * <li>OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591), section 048 * 2. 049 * </ul> 050 */ 051public class ClientMetadata { 052 053 054 /** 055 * The registered parameter names. 056 */ 057 private static final Set<String> REGISTERED_PARAMETER_NAMES; 058 059 060 /** 061 * Initialises the registered parameter name set. 062 */ 063 static { 064 Set<String> p = new HashSet<>(); 065 066 p.add("redirect_uris"); 067 p.add("scope"); 068 p.add("response_types"); 069 p.add("grant_types"); 070 p.add("contacts"); 071 p.add("client_name"); 072 p.add("logo_uri"); 073 p.add("client_uri"); 074 p.add("policy_uri"); 075 p.add("tos_uri"); 076 p.add("token_endpoint_auth_method"); 077 p.add("token_endpoint_auth_signing_alg"); 078 p.add("jwks_uri"); 079 p.add("jwks"); 080 p.add("software_id"); 081 p.add("software_version"); 082 083 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 084 } 085 086 087 /** 088 * Redirect URIs. 089 */ 090 private Set<URI> redirectURIs; 091 092 093 /** 094 * The client OAuth 2.0 scope. 095 */ 096 private Scope scope; 097 098 099 /** 100 * The expected OAuth 2.0 response types. 101 */ 102 private Set<ResponseType> responseTypes; 103 104 105 /** 106 * The expected OAuth 2.0 grant types. 107 */ 108 private Set<GrantType> grantTypes; 109 110 111 /** 112 * Administrator contacts for the client. 113 */ 114 private List<InternetAddress> contacts; 115 116 117 /** 118 * The client name. 119 */ 120 private final Map<LangTag,String> nameEntries; 121 122 123 /** 124 * The client application logo. 125 */ 126 private final Map<LangTag,URI> logoURIEntries; 127 128 129 /** 130 * The client URI entries. 131 */ 132 private final Map<LangTag,URI> uriEntries; 133 134 135 /** 136 * The client policy for use of end-user data. 137 */ 138 private Map<LangTag,URI> policyURIEntries; 139 140 141 /** 142 * The client terms of service. 143 */ 144 private final Map<LangTag,URI> tosURIEntries; 145 146 147 /** 148 * Token endpoint authentication method. 149 */ 150 private ClientAuthenticationMethod authMethod; 151 152 153 /** 154 * The JSON Web Signature (JWS) algorithm required for 155 * {@code private_key_jwt} and {@code client_secret_jwt} 156 * authentication at the Token endpoint. 157 */ 158 private JWSAlgorithm authJWSAlg; 159 160 161 /** 162 * URI for this client's JSON Web Key (JWK) set containing key(s) that 163 * are used in signing requests to the server and key(s) for encrypting 164 * responses. 165 */ 166 private URI jwkSetURI; 167 168 169 /** 170 * Client's JSON Web Key (JWK) set containing key(s) that are used in 171 * signing requests to the server and key(s) for encrypting responses. 172 * Intended as an alternative to {@link #jwkSetURI} for native clients. 173 */ 174 private JWKSet jwkSet; 175 176 177 /** 178 * Identifier for the OAuth 2.0 client software. 179 */ 180 private SoftwareID softwareID; 181 182 183 /** 184 * Version identifier for the OAuth 2.0 client software. 185 */ 186 private SoftwareVersion softwareVersion; 187 188 189 /** 190 * The custom metadata fields. 191 */ 192 private JSONObject customFields; 193 194 195 /** 196 * Creates a new OAuth 2.0 client metadata instance. 197 */ 198 public ClientMetadata() { 199 200 nameEntries = new HashMap<>(); 201 logoURIEntries = new HashMap<>(); 202 uriEntries = new HashMap<>(); 203 policyURIEntries = new HashMap<>(); 204 policyURIEntries = new HashMap<>(); 205 tosURIEntries = new HashMap<>(); 206 customFields = new JSONObject(); 207 } 208 209 210 /** 211 * Creates a shallow copy of the specified OAuth 2.0 client metadata 212 * instance. 213 * 214 * @param metadata The client metadata to copy. Must not be 215 * {@code null}. 216 */ 217 public ClientMetadata(final ClientMetadata metadata) { 218 219 redirectURIs = metadata.redirectURIs; 220 scope = metadata.scope; 221 responseTypes = metadata.responseTypes; 222 grantTypes = metadata.grantTypes; 223 contacts = metadata.contacts; 224 nameEntries = metadata.nameEntries; 225 logoURIEntries = metadata.logoURIEntries; 226 uriEntries = metadata.uriEntries; 227 policyURIEntries = metadata.policyURIEntries; 228 tosURIEntries = metadata.tosURIEntries; 229 authMethod = metadata.authMethod; 230 authJWSAlg = metadata.authJWSAlg; 231 jwkSetURI = metadata.jwkSetURI; 232 jwkSet = metadata.getJWKSet(); 233 softwareID = metadata.softwareID; 234 softwareVersion = metadata.softwareVersion; 235 customFields = metadata.customFields; 236 } 237 238 239 /** 240 * Gets the registered client metadata parameter names. 241 * 242 * @return The registered parameter names, as an unmodifiable set. 243 */ 244 public static Set<String> getRegisteredParameterNames() { 245 246 return REGISTERED_PARAMETER_NAMES; 247 } 248 249 250 /** 251 * Gets the redirection URIs for this client. Corresponds to the 252 * {@code redirect_uris} client metadata field. 253 * 254 * @return The redirection URIs, {@code null} if not specified. 255 */ 256 public Set<URI> getRedirectionURIs() { 257 258 return redirectURIs; 259 } 260 261 262 /** 263 * Gets the redirection URIs for this client as strings. Corresponds to 264 * the {@code redirect_uris} client metadata field. 265 * 266 * <p>This short-hand method is intended to enable string-based URI 267 * comparison. 268 * 269 * @return The redirection URIs as strings, {@code null} if not 270 * specified. 271 */ 272 public Set<String> getRedirectionURIStrings() { 273 274 if (redirectURIs == null) 275 return null; 276 277 Set<String> uriStrings = new HashSet<>(); 278 279 for (URI uri: redirectURIs) 280 uriStrings.add(uri.toString()); 281 282 return uriStrings; 283 } 284 285 286 /** 287 * Sets the redirection URIs for this client. Corresponds to the 288 * {@code redirect_uris} client metadata field. 289 * 290 * @param redirectURIs The redirection URIs, {@code null} if not 291 * specified. 292 */ 293 public void setRedirectionURIs(final Set<URI> redirectURIs) { 294 295 this.redirectURIs = redirectURIs; 296 } 297 298 299 /** 300 * Sets a single redirection URI for this client. Corresponds to the 301 * {@code redirect_uris} client metadata field. 302 * 303 * @param redirectURI The redirection URIs, {@code null} if not 304 * specified. 305 */ 306 public void setRedirectionURI(final URI redirectURI) { 307 308 if (redirectURI != null) { 309 redirectURIs = new HashSet<>(Collections.singletonList(redirectURI)); 310 } else { 311 redirectURIs = null; 312 } 313 } 314 315 316 /** 317 * Gets the scope values that the client can use when requesting access 318 * tokens. Corresponds to the {@code scope} client metadata field. 319 * 320 * @return The scope, {@code null} if not specified. 321 */ 322 public Scope getScope() { 323 324 return scope; 325 } 326 327 328 /** 329 * Checks if the scope matadata field is set and contains the specified 330 * scope value. 331 * 332 * @param scopeValue The scope value. Must not be {@code null}. 333 * 334 * @return {@code true} if the scope value is contained, else 335 * {@code false}. 336 */ 337 public boolean hasScopeValue(final Scope.Value scopeValue) { 338 339 return scope != null && scope.contains(scopeValue); 340 } 341 342 343 /** 344 * Sets the scope values that the client can use when requesting access 345 * tokens. Corresponds to the {@code scope} client metadata field. 346 * 347 * @param scope The scope, {@code null} if not specified. 348 */ 349 public void setScope(final Scope scope) { 350 351 this.scope = scope; 352 } 353 354 355 /** 356 * Gets the expected OAuth 2.0 response types. Corresponds to the 357 * {@code response_types} client metadata field. 358 * 359 * @return The response types, {@code null} if not specified. 360 */ 361 public Set<ResponseType> getResponseTypes() { 362 363 return responseTypes; 364 } 365 366 367 /** 368 * Sets the expected OAuth 2.0 response types. Corresponds to the 369 * {@code response_types} client metadata field. 370 * 371 * @param responseTypes The response types, {@code null} if not 372 * specified. 373 */ 374 public void setResponseTypes(final Set<ResponseType> responseTypes) { 375 376 this.responseTypes = responseTypes; 377 } 378 379 380 /** 381 * Gets the expected OAuth 2.0 grant types. Corresponds to the 382 * {@code grant_types} client metadata field. 383 * 384 * @return The grant types, {@code null} if not specified. 385 */ 386 public Set<GrantType> getGrantTypes() { 387 388 return grantTypes; 389 } 390 391 392 /** 393 * Sets the expected OAuth 2.0 grant types. Corresponds to the 394 * {@code grant_types} client metadata field. 395 * 396 * @param grantTypes The grant types, {@code null} if not specified. 397 */ 398 public void setGrantTypes(final Set<GrantType> grantTypes) { 399 400 this.grantTypes = grantTypes; 401 } 402 403 404 /** 405 * Gets the administrator contacts for the client. Corresponds to the 406 * {@code contacts} client metadata field. 407 * 408 * @return The administrator contacts, {@code null} if not specified. 409 */ 410 public List<InternetAddress> getContacts() { 411 412 return contacts; 413 } 414 415 416 /** 417 * Sets the administrator contacts for the client. Corresponds to the 418 * {@code contacts} client metadata field. 419 * 420 * @param contacts The administrator contacts, {@code null} if not 421 * specified. 422 */ 423 public void setContacts(final List<InternetAddress> contacts) { 424 425 this.contacts = contacts; 426 } 427 428 429 /** 430 * Gets the client name. Corresponds to the {@code client_name} client 431 * metadata field, with no language tag. 432 * 433 * @return The client name, {@code null} if not specified. 434 */ 435 public String getName() { 436 437 return getName(null); 438 } 439 440 441 /** 442 * Gets the client name. Corresponds to the {@code client_name} client 443 * metadata field, with an optional language tag. 444 * 445 * @param langTag The language tag of the entry, {@code null} to get 446 * the non-tagged entry. 447 * 448 * @return The client name, {@code null} if not specified. 449 */ 450 public String getName(final LangTag langTag) { 451 452 return nameEntries.get(langTag); 453 } 454 455 456 /** 457 * Gets the client name entries. Corresponds to the {@code client_name} 458 * client metadata field. 459 * 460 * @return The client name entries, empty map if none. 461 */ 462 public Map<LangTag,String> getNameEntries() { 463 464 return nameEntries; 465 } 466 467 468 /** 469 * Sets the client name. Corresponds to the {@code client_name} client 470 * metadata field, with no language tag. 471 * 472 * @param name The client name, {@code null} if not specified. 473 */ 474 public void setName(final String name) { 475 476 nameEntries.put(null, name); 477 } 478 479 480 /** 481 * Sets the client name. Corresponds to the {@code client_name} client 482 * metadata field, with an optional language tag. 483 * 484 * @param name The client name. Must not be {@code null}. 485 * @param langTag The language tag, {@code null} if not specified. 486 */ 487 public void setName(final String name, final LangTag langTag) { 488 489 nameEntries.put(langTag, name); 490 } 491 492 493 /** 494 * Gets the client application logo. Corresponds to the 495 * {@code logo_uri} client metadata field, with no language 496 * tag. 497 * 498 * @return The logo URI, {@code null} if not specified. 499 */ 500 public URI getLogoURI() { 501 502 return getLogoURI(null); 503 } 504 505 506 /** 507 * Gets the client application logo. Corresponds to the 508 * {@code logo_uri} client metadata field, with an optional 509 * language tag. 510 * 511 * @return The logo URI, {@code null} if not specified. 512 */ 513 public URI getLogoURI(final LangTag langTag) { 514 515 return logoURIEntries.get(langTag); 516 } 517 518 519 /** 520 * Gets the client application logo entries. Corresponds to the 521 * {@code logo_uri} client metadata field. 522 * 523 * @return The logo URI entries, empty map if none. 524 */ 525 public Map<LangTag,URI> getLogoURIEntries() { 526 527 return logoURIEntries; 528 } 529 530 531 /** 532 * Sets the client application logo. Corresponds to the 533 * {@code logo_uri} client metadata field, with no language 534 * tag. 535 * 536 * @param logoURI The logo URI, {@code null} if not specified. 537 */ 538 public void setLogoURI(final URI logoURI) { 539 540 logoURIEntries.put(null, logoURI); 541 } 542 543 544 /** 545 * Sets the client application logo. Corresponds to the 546 * {@code logo_uri} client metadata field, with an optional 547 * language tag. 548 * 549 * @param logoURI The logo URI. Must not be {@code null}. 550 * @param langTag The language tag, {@code null} if not specified. 551 */ 552 public void setLogoURI(final URI logoURI, final LangTag langTag) { 553 554 logoURIEntries.put(langTag, logoURI); 555 } 556 557 558 /** 559 * Gets the client home page. Corresponds to the {@code client_uri} 560 * client metadata field, with no language tag. 561 * 562 * @return The client URI, {@code null} if not specified. 563 */ 564 public URI getURI() { 565 566 return getURI(null); 567 } 568 569 570 /** 571 * Gets the client home page. Corresponds to the {@code client_uri} 572 * client metadata field, with an optional language tag. 573 * 574 * @return The client URI, {@code null} if not specified. 575 */ 576 public URI getURI(final LangTag langTag) { 577 578 return uriEntries.get(langTag); 579 } 580 581 582 /** 583 * Gets the client home page entries. Corresponds to the 584 * {@code client_uri} client metadata field. 585 * 586 * @return The client URI entries, empty map if none. 587 */ 588 public Map<LangTag,URI> getURIEntries() { 589 590 return uriEntries; 591 } 592 593 594 /** 595 * Sets the client home page. Corresponds to the {@code client_uri} 596 * client metadata field, with no language tag. 597 * 598 * @param uri The client URI, {@code null} if not specified. 599 */ 600 public void setURI(final URI uri) { 601 602 uriEntries.put(null, uri); 603 } 604 605 606 /** 607 * Sets the client home page. Corresponds to the {@code client_uri} 608 * client metadata field, with an optional language tag. 609 * 610 * @param uri The URI. Must not be {@code null}. 611 * @param langTag The language tag, {@code null} if not specified. 612 */ 613 public void setURI(final URI uri, final LangTag langTag) { 614 615 uriEntries.put(langTag, uri); 616 } 617 618 619 /** 620 * Gets the client policy for use of end-user data. Corresponds to the 621 * {@code policy_uri} client metadata field, with no language 622 * tag. 623 * 624 * @return The policy URI, {@code null} if not specified. 625 */ 626 public URI getPolicyURI() { 627 628 return getPolicyURI(null); 629 } 630 631 632 /** 633 * Gets the client policy for use of end-user data. Corresponds to the 634 * {@code policy_uri} client metadata field, with an optional 635 * language tag. 636 * 637 * @return The policy URI, {@code null} if not specified. 638 */ 639 public URI getPolicyURI(final LangTag langTag) { 640 641 return policyURIEntries.get(langTag); 642 } 643 644 645 /** 646 * Gets the client policy entries for use of end-user data. 647 * Corresponds to the {@code policy_uri} client metadata field. 648 * 649 * @return The policy URI entries, empty map if none. 650 */ 651 public Map<LangTag,URI> getPolicyURIEntries() { 652 653 return policyURIEntries; 654 } 655 656 657 /** 658 * Sets the client policy for use of end-user data. Corresponds to the 659 * {@code policy_uri} client metadata field, with no language 660 * tag. 661 * 662 * @param policyURI The policy URI, {@code null} if not specified. 663 */ 664 public void setPolicyURI(final URI policyURI) { 665 666 policyURIEntries.put(null, policyURI); 667 } 668 669 670 /** 671 * Sets the client policy for use of end-user data. Corresponds to the 672 * {@code policy_uri} client metadata field, with an optional 673 * language tag. 674 * 675 * @param policyURI The policy URI. Must not be {@code null}. 676 * @param langTag The language tag, {@code null} if not specified. 677 */ 678 public void setPolicyURI(final URI policyURI, final LangTag langTag) { 679 680 policyURIEntries.put(langTag, policyURI); 681 } 682 683 684 /** 685 * Gets the client's terms of service. Corresponds to the 686 * {@code tos_uri} client metadata field, with no language 687 * tag. 688 * 689 * @return The terms of service URI, {@code null} if not specified. 690 */ 691 public URI getTermsOfServiceURI() { 692 693 return getTermsOfServiceURI(null); 694 } 695 696 697 /** 698 * Gets the client's terms of service. Corresponds to the 699 * {@code tos_uri} client metadata field, with an optional 700 * language tag. 701 * 702 * @return The terms of service URI, {@code null} if not specified. 703 */ 704 public URI getTermsOfServiceURI(final LangTag langTag) { 705 706 return tosURIEntries.get(langTag); 707 } 708 709 710 /** 711 * Gets the client's terms of service entries. Corresponds to the 712 * {@code tos_uri} client metadata field. 713 * 714 * @return The terms of service URI entries, empty map if none. 715 */ 716 public Map<LangTag,URI> getTermsOfServiceURIEntries() { 717 718 return tosURIEntries; 719 } 720 721 722 /** 723 * Sets the client's terms of service. Corresponds to the 724 * {@code tos_uri} client metadata field, with no language 725 * tag. 726 * 727 * @param tosURI The terms of service URI, {@code null} if not 728 * specified. 729 */ 730 public void setTermsOfServiceURI(final URI tosURI) { 731 732 tosURIEntries.put(null, tosURI); 733 } 734 735 736 /** 737 * Sets the client's terms of service. Corresponds to the 738 * {@code tos_uri} client metadata field, with an optional 739 * language tag. 740 * 741 * @param tosURI The terms of service URI. Must not be {@code null}. 742 * @param langTag The language tag, {@code null} if not specified. 743 */ 744 public void setTermsOfServiceURI(final URI tosURI, final LangTag langTag) { 745 746 tosURIEntries.put(langTag, tosURI); 747 } 748 749 750 /** 751 * Gets the Token endpoint authentication method. Corresponds to the 752 * {@code token_endpoint_auth_method} client metadata field. 753 * 754 * @return The Token endpoint authentication method, {@code null} if 755 * not specified. 756 */ 757 public ClientAuthenticationMethod getTokenEndpointAuthMethod() { 758 759 return authMethod; 760 } 761 762 763 /** 764 * Sets the Token endpoint authentication method. Corresponds to the 765 * {@code token_endpoint_auth_method} client metadata field. 766 * 767 * @param authMethod The Token endpoint authentication method, 768 * {@code null} if not specified. 769 */ 770 public void setTokenEndpointAuthMethod(final ClientAuthenticationMethod authMethod) { 771 772 this.authMethod = authMethod; 773 } 774 775 776 /** 777 * Gets the JSON Web Signature (JWS) algorithm required for 778 * {@code private_key_jwt} and {@code client_secret_jwt} 779 * authentication at the Token endpoint. Corresponds to the 780 * {@code token_endpoint_auth_signing_alg} client metadata field. 781 * 782 * @return The JWS algorithm, {@code null} if not specified. 783 */ 784 public JWSAlgorithm getTokenEndpointAuthJWSAlg() { 785 786 return authJWSAlg; 787 } 788 789 790 /** 791 * Sets the JSON Web Signature (JWS) algorithm required for 792 * {@code private_key_jwt} and {@code client_secret_jwt} 793 * authentication at the Token endpoint. Corresponds to the 794 * {@code token_endpoint_auth_signing_alg} client metadata field. 795 * 796 * @param authJWSAlg The JWS algorithm, {@code null} if not specified. 797 */ 798 public void setTokenEndpointAuthJWSAlg(final JWSAlgorithm authJWSAlg) { 799 800 this.authJWSAlg = authJWSAlg; 801 } 802 803 804 /** 805 * Gets the URI for this client's JSON Web Key (JWK) set containing 806 * key(s) that are used in signing requests to the server and key(s) 807 * for encrypting responses. Corresponds to the {@code jwks_uri} client 808 * metadata field. 809 * 810 * @return The JWK set URI, {@code null} if not specified. 811 */ 812 public URI getJWKSetURI() { 813 814 return jwkSetURI; 815 } 816 817 818 /** 819 * Sets the URI for this client's JSON Web Key (JWK) set containing 820 * key(s) that are used in signing requests to the server and key(s) 821 * for encrypting responses. Corresponds to the {@code jwks_uri} client 822 * metadata field. 823 * 824 * @param jwkSetURI The JWK set URI, {@code null} if not specified. 825 */ 826 public void setJWKSetURI(final URI jwkSetURI) { 827 828 this.jwkSetURI = jwkSetURI; 829 } 830 831 832 /** 833 * Gets this client's JSON Web Key (JWK) set containing key(s) that are 834 * used in signing requests to the server and key(s) for encrypting 835 * responses. Intended as an alternative to {@link #getJWKSetURI} for 836 * native clients. Corresponds to the {@code jwks} client metadata 837 * field. 838 * 839 * @return The JWK set, {@code null} if not specified. 840 */ 841 public JWKSet getJWKSet() { 842 843 return jwkSet; 844 } 845 846 847 /** 848 * Sets this client's JSON Web Key (JWK) set containing key(s) that are 849 * used in signing requests to the server and key(s) for encrypting 850 * responses. Intended as an alternative to {@link #getJWKSetURI} for 851 * native clients. Corresponds to the {@code jwks} client metadata 852 * field. 853 * 854 * @param jwkSet The JWK set, {@code null} if not specified. 855 */ 856 public void setJWKSet(final JWKSet jwkSet) { 857 858 this.jwkSet = jwkSet; 859 } 860 861 862 /** 863 * Gets the identifier for the OAuth 2.0 client software. Corresponds 864 * to the {@code software_id} client metadata field. 865 * 866 * @return The software identifier, {@code null} if not specified. 867 */ 868 public SoftwareID getSoftwareID() { 869 870 return softwareID; 871 } 872 873 874 /** 875 * Sets the identifier for the OAuth 2.0 client software. Corresponds 876 * to the {@code software_id} client metadata field. 877 * 878 * @param softwareID The software identifier, {@code null} if not 879 * specified. 880 */ 881 public void setSoftwareID(final SoftwareID softwareID) { 882 883 this.softwareID = softwareID; 884 } 885 886 887 /** 888 * Gets the version identifier for the OAuth 2.0 client software. 889 * Corresponds to the {@code software_version} client metadata field. 890 * 891 * @return The version identifier, {@code null} if not specified. 892 */ 893 public SoftwareVersion getSoftwareVersion() { 894 895 return softwareVersion; 896 } 897 898 899 /** 900 * Sets the version identifier for the OAuth 2.0 client software. 901 * Corresponds to the {@code software_version} client metadata field. 902 * 903 * @param softwareVersion The version identifier, {@code null} if not 904 * specified. 905 */ 906 public void setSoftwareVersion(final SoftwareVersion softwareVersion) { 907 908 this.softwareVersion = softwareVersion; 909 } 910 911 912 /** 913 * Gets the specified custom metadata field. 914 * 915 * @param name The field name. Must not be {@code null}. 916 * 917 * @return The field value, typically serialisable to a JSON entity, 918 * {@code null} if none. 919 */ 920 public Object getCustomField(final String name) { 921 922 return customFields.get(name); 923 } 924 925 926 /** 927 * Gets the custom metadata fields. 928 * 929 * @return The custom metadata fields, as a JSON object, empty object 930 * if none. 931 */ 932 public JSONObject getCustomFields() { 933 934 return customFields; 935 } 936 937 938 /** 939 * Sets the specified custom metadata field. 940 * 941 * @param name The field name. Must not be {@code null}. 942 * @param value The field value. Should serialise to a JSON entity. 943 */ 944 public void setCustomField(final String name, final Object value) { 945 946 customFields.put(name, value); 947 } 948 949 950 /** 951 * Sets the custom metadata fields. 952 * 953 * @param customFields The custom metadata fields, as a JSON object, 954 * empty object if none. Must not be {@code null}. 955 */ 956 public void setCustomFields(final JSONObject customFields) { 957 958 if (customFields == null) 959 throw new IllegalArgumentException("The custom fields JSON object must not be null"); 960 961 this.customFields = customFields; 962 } 963 964 965 /** 966 * Applies the client metadata defaults where no values have been 967 * specified. 968 * 969 * <ul> 970 * <li>The response types default to {@code ["code"]}. 971 * <li>The grant types default to {@code ["authorization_code"]}. 972 * <li>The client authentication method defaults to 973 * "client_secret_basic", unless the grant type is "implicit" 974 * only. 975 * </ul> 976 */ 977 public void applyDefaults() { 978 979 if (responseTypes == null) { 980 responseTypes = new HashSet<>(); 981 responseTypes.add(ResponseType.getDefault()); 982 } 983 984 if (grantTypes == null) { 985 grantTypes = new HashSet<>(); 986 grantTypes.add(GrantType.AUTHORIZATION_CODE); 987 } 988 989 if (authMethod == null) { 990 991 if (grantTypes.contains(GrantType.IMPLICIT) && grantTypes.size() == 1) { 992 authMethod = ClientAuthenticationMethod.NONE; 993 } else { 994 authMethod = ClientAuthenticationMethod.getDefault(); 995 } 996 } 997 } 998 999 1000 /** 1001 * Returns the JSON object representation of this client metadata, 1002 * including any custom fields. 1003 * 1004 * @return The JSON object. 1005 */ 1006 public JSONObject toJSONObject() { 1007 1008 return toJSONObject(true); 1009 } 1010 1011 1012 /** 1013 * Returns the JSON object representation of this client metadata. 1014 * 1015 * @param includeCustomFields {@code true} to include any custom 1016 * metadata fields, {@code false} to omit 1017 * them. 1018 * 1019 * @return The JSON object. 1020 */ 1021 public JSONObject toJSONObject(final boolean includeCustomFields) { 1022 1023 JSONObject o; 1024 1025 if (includeCustomFields) 1026 o = new JSONObject(customFields); 1027 else 1028 o = new JSONObject(); 1029 1030 1031 if (redirectURIs != null) { 1032 1033 JSONArray uriList = new JSONArray(); 1034 1035 for (URI uri: redirectURIs) 1036 uriList.add(uri.toString()); 1037 1038 o.put("redirect_uris", uriList); 1039 } 1040 1041 1042 if (scope != null) 1043 o.put("scope", scope.toString()); 1044 1045 1046 if (responseTypes != null) { 1047 1048 JSONArray rtList = new JSONArray(); 1049 1050 for (ResponseType rt: responseTypes) 1051 rtList.add(rt.toString()); 1052 1053 o.put("response_types", rtList); 1054 } 1055 1056 1057 if (grantTypes != null) { 1058 1059 JSONArray grantList = new JSONArray(); 1060 1061 for (GrantType grant: grantTypes) 1062 grantList.add(grant.toString()); 1063 1064 o.put("grant_types", grantList); 1065 } 1066 1067 1068 if (contacts != null) { 1069 1070 JSONArray contactList = new JSONArray(); 1071 1072 for (InternetAddress email: contacts) 1073 contactList.add(email.toString()); 1074 1075 o.put("contacts", contactList); 1076 } 1077 1078 1079 if (! nameEntries.isEmpty()) { 1080 1081 for (Map.Entry<LangTag,String> entry: nameEntries.entrySet()) { 1082 1083 LangTag langTag = entry.getKey(); 1084 String name = entry.getValue(); 1085 1086 if (name == null) 1087 continue; 1088 1089 if (langTag == null) 1090 o.put("client_name", entry.getValue()); 1091 else 1092 o.put("client_name#" + langTag, entry.getValue()); 1093 } 1094 } 1095 1096 1097 if (! logoURIEntries.isEmpty()) { 1098 1099 for (Map.Entry<LangTag,URI> entry: logoURIEntries.entrySet()) { 1100 1101 LangTag langTag = entry.getKey(); 1102 URI uri = entry.getValue(); 1103 1104 if (uri == null) 1105 continue; 1106 1107 if (langTag == null) 1108 o.put("logo_uri", entry.getValue().toString()); 1109 else 1110 o.put("logo_uri#" + langTag, entry.getValue().toString()); 1111 } 1112 } 1113 1114 1115 if (! uriEntries.isEmpty()) { 1116 1117 for (Map.Entry<LangTag,URI> entry: uriEntries.entrySet()) { 1118 1119 LangTag langTag = entry.getKey(); 1120 URI uri = entry.getValue(); 1121 1122 if (uri == null) 1123 continue; 1124 1125 if (langTag == null) 1126 o.put("client_uri", entry.getValue().toString()); 1127 else 1128 o.put("client_uri#" + langTag, entry.getValue().toString()); 1129 } 1130 } 1131 1132 1133 if (! policyURIEntries.isEmpty()) { 1134 1135 for (Map.Entry<LangTag,URI> entry: policyURIEntries.entrySet()) { 1136 1137 LangTag langTag = entry.getKey(); 1138 URI uri = entry.getValue(); 1139 1140 if (uri == null) 1141 continue; 1142 1143 if (langTag == null) 1144 o.put("policy_uri", entry.getValue().toString()); 1145 else 1146 o.put("policy_uri#" + langTag, entry.getValue().toString()); 1147 } 1148 } 1149 1150 1151 if (! tosURIEntries.isEmpty()) { 1152 1153 for (Map.Entry<LangTag,URI> entry: tosURIEntries.entrySet()) { 1154 1155 LangTag langTag = entry.getKey(); 1156 URI uri = entry.getValue(); 1157 1158 if (uri == null) 1159 continue; 1160 1161 if (langTag == null) 1162 o.put("tos_uri", entry.getValue().toString()); 1163 else 1164 o.put("tos_uri#" + langTag, entry.getValue().toString()); 1165 } 1166 } 1167 1168 1169 if (authMethod != null) 1170 o.put("token_endpoint_auth_method", authMethod.toString()); 1171 1172 1173 if (authJWSAlg != null) 1174 o.put("token_endpoint_auth_signing_alg", authJWSAlg.getName()); 1175 1176 1177 if (jwkSetURI != null) 1178 o.put("jwks_uri", jwkSetURI.toString()); 1179 1180 1181 if (jwkSet != null) 1182 o.put("jwks", jwkSet.toJSONObject(true)); // prevent private keys from leaking 1183 1184 1185 if (softwareID != null) 1186 o.put("software_id", softwareID.getValue()); 1187 1188 if (softwareVersion != null) 1189 o.put("software_version", softwareVersion.getValue()); 1190 1191 return o; 1192 } 1193 1194 1195 /** 1196 * Parses an client metadata instance from the specified JSON object. 1197 * 1198 * @param jsonObject The JSON object to parse. Must not be 1199 * {@code null}. 1200 * 1201 * @return The client metadata. 1202 * 1203 * @throws ParseException If the JSON object couldn't be parsed to a 1204 * client metadata instance. 1205 */ 1206 public static ClientMetadata parse(final JSONObject jsonObject) 1207 throws ParseException { 1208 1209 // Copy JSON object, then parse 1210 return parseFromModifiableJSONObject(new JSONObject(jsonObject)); 1211 } 1212 1213 1214 /** 1215 * Parses an client metadata instance from the specified JSON object. 1216 * 1217 * @param jsonObject The JSON object to parse, will be modified by 1218 * the parse routine. Must not be {@code null}. 1219 * 1220 * @return The client metadata. 1221 * 1222 * @throws ParseException If the JSON object couldn't be parsed to a 1223 * client metadata instance. 1224 */ 1225 private static ClientMetadata parseFromModifiableJSONObject(final JSONObject jsonObject) 1226 throws ParseException { 1227 1228 ClientMetadata metadata = new ClientMetadata(); 1229 1230 if (jsonObject.containsKey("redirect_uris")) { 1231 1232 Set<URI> redirectURIs = new LinkedHashSet<>(); 1233 1234 for (String uriString: JSONObjectUtils.getStringArray(jsonObject, "redirect_uris")) { 1235 1236 try { 1237 redirectURIs.add(new URI(uriString)); 1238 1239 } catch (URISyntaxException e) { 1240 1241 throw new ParseException("Invalid \"redirect_uris\" parameter: " + 1242 e.getMessage()); 1243 } 1244 } 1245 1246 metadata.setRedirectionURIs(redirectURIs); 1247 jsonObject.remove("redirect_uris"); 1248 } 1249 1250 1251 if (jsonObject.containsKey("scope")) { 1252 metadata.setScope(Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"))); 1253 jsonObject.remove("scope"); 1254 } 1255 1256 1257 if (jsonObject.containsKey("response_types")) { 1258 1259 Set<ResponseType> responseTypes = new LinkedHashSet<>(); 1260 1261 for (String rt: JSONObjectUtils.getStringArray(jsonObject, "response_types")) { 1262 1263 responseTypes.add(ResponseType.parse(rt)); 1264 } 1265 1266 metadata.setResponseTypes(responseTypes); 1267 jsonObject.remove("response_types"); 1268 } 1269 1270 1271 if (jsonObject.containsKey("grant_types")) { 1272 1273 Set<GrantType> grantTypes = new LinkedHashSet<>(); 1274 1275 for (String grant: JSONObjectUtils.getStringArray(jsonObject, "grant_types")) { 1276 1277 grantTypes.add(GrantType.parse(grant)); 1278 } 1279 1280 metadata.setGrantTypes(grantTypes); 1281 jsonObject.remove("grant_types"); 1282 } 1283 1284 1285 if (jsonObject.containsKey("contacts")) { 1286 1287 List<InternetAddress> emailList = new LinkedList<>(); 1288 1289 for (String emailString: JSONObjectUtils.getStringArray(jsonObject, "contacts")) { 1290 1291 try { 1292 emailList.add(new InternetAddress(emailString)); 1293 1294 } catch (AddressException e) { 1295 1296 throw new ParseException("Invalid \"contacts\" parameter: " + 1297 e.getMessage()); 1298 } 1299 } 1300 1301 metadata.setContacts(emailList); 1302 jsonObject.remove("contacts"); 1303 } 1304 1305 1306 // Find lang-tagged client_name params 1307 Map<LangTag,Object> matches = LangTagUtils.find("client_name", jsonObject); 1308 1309 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1310 1311 try { 1312 metadata.setName((String)entry.getValue(), entry.getKey()); 1313 1314 } catch (ClassCastException e) { 1315 1316 throw new ParseException("Invalid \"client_name\" (language tag) parameter"); 1317 } 1318 1319 removeMember(jsonObject, "client_name", entry.getKey()); 1320 } 1321 1322 1323 matches = LangTagUtils.find("logo_uri", jsonObject); 1324 1325 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1326 1327 try { 1328 metadata.setLogoURI(new URI((String)entry.getValue()), entry.getKey()); 1329 1330 } catch (Exception e) { 1331 1332 throw new ParseException("Invalid \"logo_uri\" (language tag) parameter"); 1333 } 1334 1335 removeMember(jsonObject, "logo_uri", entry.getKey()); 1336 } 1337 1338 1339 matches = LangTagUtils.find("client_uri", jsonObject); 1340 1341 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1342 1343 try { 1344 metadata.setURI(new URI((String)entry.getValue()), entry.getKey()); 1345 1346 1347 } catch (Exception e) { 1348 1349 throw new ParseException("Invalid \"client_uri\" (language tag) parameter"); 1350 } 1351 1352 removeMember(jsonObject, "client_uri", entry.getKey()); 1353 } 1354 1355 1356 matches = LangTagUtils.find("policy_uri", jsonObject); 1357 1358 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1359 1360 try { 1361 metadata.setPolicyURI(new URI((String)entry.getValue()), entry.getKey()); 1362 1363 } catch (Exception e) { 1364 1365 throw new ParseException("Invalid \"policy_uri\" (language tag) parameter"); 1366 } 1367 1368 removeMember(jsonObject, "policy_uri", entry.getKey()); 1369 } 1370 1371 1372 matches = LangTagUtils.find("tos_uri", jsonObject); 1373 1374 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1375 1376 try { 1377 metadata.setTermsOfServiceURI(new URI((String)entry.getValue()), entry.getKey()); 1378 1379 } catch (Exception e) { 1380 1381 throw new ParseException("Invalid \"tos_uri\" (language tag) parameter"); 1382 } 1383 1384 removeMember(jsonObject, "tos_uri", entry.getKey()); 1385 } 1386 1387 1388 if (jsonObject.containsKey("token_endpoint_auth_method")) { 1389 metadata.setTokenEndpointAuthMethod(new ClientAuthenticationMethod( 1390 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_method"))); 1391 1392 jsonObject.remove("token_endpoint_auth_method"); 1393 } 1394 1395 1396 if (jsonObject.containsKey("token_endpoint_auth_signing_alg")) { 1397 metadata.setTokenEndpointAuthJWSAlg(new JWSAlgorithm( 1398 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_signing_alg"))); 1399 1400 jsonObject.remove("token_endpoint_auth_signing_alg"); 1401 } 1402 1403 1404 if (jsonObject.containsKey("jwks_uri")) { 1405 metadata.setJWKSetURI(JSONObjectUtils.getURI(jsonObject, "jwks_uri")); 1406 jsonObject.remove("jwks_uri"); 1407 } 1408 1409 if (jsonObject.containsKey("jwks")) { 1410 1411 try { 1412 metadata.setJWKSet(JWKSet.parse(JSONObjectUtils.getJSONObject(jsonObject, "jwks"))); 1413 1414 } catch (java.text.ParseException e) { 1415 throw new ParseException(e.getMessage(), e); 1416 } 1417 1418 jsonObject.remove("jwks"); 1419 } 1420 1421 if (jsonObject.containsKey("software_id")) { 1422 metadata.setSoftwareID(new SoftwareID(JSONObjectUtils.getString(jsonObject, "software_id"))); 1423 jsonObject.remove("software_id"); 1424 } 1425 1426 if (jsonObject.containsKey("software_version")) { 1427 metadata.setSoftwareVersion(new SoftwareVersion(JSONObjectUtils.getString(jsonObject, "software_version"))); 1428 jsonObject.remove("software_version"); 1429 } 1430 1431 // The remaining fields are custom 1432 metadata.customFields = jsonObject; 1433 1434 return metadata; 1435 } 1436 1437 1438 /** 1439 * Removes a JSON object member with the specified base name and 1440 * optional language tag. 1441 * 1442 * @param jsonObject The JSON object. Must not be {@code null}. 1443 * @param name The base member name. Must not be {@code null}. 1444 * @param langTag The language tag, {@code null} if none. 1445 */ 1446 private static void removeMember(final JSONObject jsonObject, final String name, final LangTag langTag) { 1447 1448 if (langTag == null) 1449 jsonObject.remove(name); 1450 else 1451 jsonObject.remove(name + "#" + langTag); 1452 } 1453}