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". 974 * </ul> 975 */ 976 public void applyDefaults() { 977 978 if (responseTypes == null) { 979 responseTypes = new HashSet<>(); 980 responseTypes.add(ResponseType.getDefault()); 981 } 982 983 if (grantTypes == null) { 984 grantTypes = new HashSet<>(); 985 grantTypes.add(GrantType.AUTHORIZATION_CODE); 986 } 987 988 if (authMethod == null) { 989 authMethod = ClientAuthenticationMethod.getDefault(); 990 } 991 } 992 993 994 /** 995 * Returns the JSON object representation of this client metadata, 996 * including any custom fields. 997 * 998 * @return The JSON object. 999 */ 1000 public JSONObject toJSONObject() { 1001 1002 return toJSONObject(true); 1003 } 1004 1005 1006 /** 1007 * Returns the JSON object representation of this client metadata. 1008 * 1009 * @param includeCustomFields {@code true} to include any custom 1010 * metadata fields, {@code false} to omit 1011 * them. 1012 * 1013 * @return The JSON object. 1014 */ 1015 public JSONObject toJSONObject(final boolean includeCustomFields) { 1016 1017 JSONObject o; 1018 1019 if (includeCustomFields) 1020 o = new JSONObject(customFields); 1021 else 1022 o = new JSONObject(); 1023 1024 1025 if (redirectURIs != null) { 1026 1027 JSONArray uriList = new JSONArray(); 1028 1029 for (URI uri: redirectURIs) 1030 uriList.add(uri.toString()); 1031 1032 o.put("redirect_uris", uriList); 1033 } 1034 1035 1036 if (scope != null) 1037 o.put("scope", scope.toString()); 1038 1039 1040 if (responseTypes != null) { 1041 1042 JSONArray rtList = new JSONArray(); 1043 1044 for (ResponseType rt: responseTypes) 1045 rtList.add(rt.toString()); 1046 1047 o.put("response_types", rtList); 1048 } 1049 1050 1051 if (grantTypes != null) { 1052 1053 JSONArray grantList = new JSONArray(); 1054 1055 for (GrantType grant: grantTypes) 1056 grantList.add(grant.toString()); 1057 1058 o.put("grant_types", grantList); 1059 } 1060 1061 1062 if (contacts != null) { 1063 1064 JSONArray contactList = new JSONArray(); 1065 1066 for (InternetAddress email: contacts) 1067 contactList.add(email.toString()); 1068 1069 o.put("contacts", contactList); 1070 } 1071 1072 1073 if (! nameEntries.isEmpty()) { 1074 1075 for (Map.Entry<LangTag,String> entry: nameEntries.entrySet()) { 1076 1077 LangTag langTag = entry.getKey(); 1078 String name = entry.getValue(); 1079 1080 if (name == null) 1081 continue; 1082 1083 if (langTag == null) 1084 o.put("client_name", entry.getValue()); 1085 else 1086 o.put("client_name#" + langTag, entry.getValue()); 1087 } 1088 } 1089 1090 1091 if (! logoURIEntries.isEmpty()) { 1092 1093 for (Map.Entry<LangTag,URI> entry: logoURIEntries.entrySet()) { 1094 1095 LangTag langTag = entry.getKey(); 1096 URI uri = entry.getValue(); 1097 1098 if (uri == null) 1099 continue; 1100 1101 if (langTag == null) 1102 o.put("logo_uri", entry.getValue().toString()); 1103 else 1104 o.put("logo_uri#" + langTag, entry.getValue().toString()); 1105 } 1106 } 1107 1108 1109 if (! uriEntries.isEmpty()) { 1110 1111 for (Map.Entry<LangTag,URI> entry: uriEntries.entrySet()) { 1112 1113 LangTag langTag = entry.getKey(); 1114 URI uri = entry.getValue(); 1115 1116 if (uri == null) 1117 continue; 1118 1119 if (langTag == null) 1120 o.put("client_uri", entry.getValue().toString()); 1121 else 1122 o.put("client_uri#" + langTag, entry.getValue().toString()); 1123 } 1124 } 1125 1126 1127 if (! policyURIEntries.isEmpty()) { 1128 1129 for (Map.Entry<LangTag,URI> entry: policyURIEntries.entrySet()) { 1130 1131 LangTag langTag = entry.getKey(); 1132 URI uri = entry.getValue(); 1133 1134 if (uri == null) 1135 continue; 1136 1137 if (langTag == null) 1138 o.put("policy_uri", entry.getValue().toString()); 1139 else 1140 o.put("policy_uri#" + langTag, entry.getValue().toString()); 1141 } 1142 } 1143 1144 1145 if (! tosURIEntries.isEmpty()) { 1146 1147 for (Map.Entry<LangTag,URI> entry: tosURIEntries.entrySet()) { 1148 1149 LangTag langTag = entry.getKey(); 1150 URI uri = entry.getValue(); 1151 1152 if (uri == null) 1153 continue; 1154 1155 if (langTag == null) 1156 o.put("tos_uri", entry.getValue().toString()); 1157 else 1158 o.put("tos_uri#" + langTag, entry.getValue().toString()); 1159 } 1160 } 1161 1162 1163 if (authMethod != null) 1164 o.put("token_endpoint_auth_method", authMethod.toString()); 1165 1166 1167 if (authJWSAlg != null) 1168 o.put("token_endpoint_auth_signing_alg", authJWSAlg.getName()); 1169 1170 1171 if (jwkSetURI != null) 1172 o.put("jwks_uri", jwkSetURI.toString()); 1173 1174 1175 if (jwkSet != null) 1176 o.put("jwks", jwkSet.toJSONObject(true)); // prevent private keys from leaking 1177 1178 1179 if (softwareID != null) 1180 o.put("software_id", softwareID.getValue()); 1181 1182 if (softwareVersion != null) 1183 o.put("software_version", softwareVersion.getValue()); 1184 1185 return o; 1186 } 1187 1188 1189 /** 1190 * Parses an client metadata instance from the specified JSON object. 1191 * 1192 * @param jsonObject The JSON object to parse. Must not be 1193 * {@code null}. 1194 * 1195 * @return The client metadata. 1196 * 1197 * @throws ParseException If the JSON object couldn't be parsed to a 1198 * client metadata instance. 1199 */ 1200 public static ClientMetadata parse(final JSONObject jsonObject) 1201 throws ParseException { 1202 1203 // Copy JSON object, then parse 1204 return parseFromModifiableJSONObject(new JSONObject(jsonObject)); 1205 } 1206 1207 1208 /** 1209 * Parses an client metadata instance from the specified JSON object. 1210 * 1211 * @param jsonObject The JSON object to parse, will be modified by 1212 * the parse routine. Must not be {@code null}. 1213 * 1214 * @return The client metadata. 1215 * 1216 * @throws ParseException If the JSON object couldn't be parsed to a 1217 * client metadata instance. 1218 */ 1219 private static ClientMetadata parseFromModifiableJSONObject(final JSONObject jsonObject) 1220 throws ParseException { 1221 1222 ClientMetadata metadata = new ClientMetadata(); 1223 1224 if (jsonObject.containsKey("redirect_uris")) { 1225 1226 Set<URI> redirectURIs = new LinkedHashSet<>(); 1227 1228 for (String uriString: JSONObjectUtils.getStringArray(jsonObject, "redirect_uris")) { 1229 1230 try { 1231 redirectURIs.add(new URI(uriString)); 1232 1233 } catch (URISyntaxException e) { 1234 1235 throw new ParseException("Invalid \"redirect_uris\" parameter: " + 1236 e.getMessage()); 1237 } 1238 } 1239 1240 metadata.setRedirectionURIs(redirectURIs); 1241 jsonObject.remove("redirect_uris"); 1242 } 1243 1244 1245 if (jsonObject.containsKey("scope")) { 1246 metadata.setScope(Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"))); 1247 jsonObject.remove("scope"); 1248 } 1249 1250 1251 if (jsonObject.containsKey("response_types")) { 1252 1253 Set<ResponseType> responseTypes = new LinkedHashSet<>(); 1254 1255 for (String rt: JSONObjectUtils.getStringArray(jsonObject, "response_types")) { 1256 1257 responseTypes.add(ResponseType.parse(rt)); 1258 } 1259 1260 metadata.setResponseTypes(responseTypes); 1261 jsonObject.remove("response_types"); 1262 } 1263 1264 1265 if (jsonObject.containsKey("grant_types")) { 1266 1267 Set<GrantType> grantTypes = new LinkedHashSet<>(); 1268 1269 for (String grant: JSONObjectUtils.getStringArray(jsonObject, "grant_types")) { 1270 1271 grantTypes.add(GrantType.parse(grant)); 1272 } 1273 1274 metadata.setGrantTypes(grantTypes); 1275 jsonObject.remove("grant_types"); 1276 } 1277 1278 1279 if (jsonObject.containsKey("contacts")) { 1280 1281 List<InternetAddress> emailList = new LinkedList<>(); 1282 1283 for (String emailString: JSONObjectUtils.getStringArray(jsonObject, "contacts")) { 1284 1285 try { 1286 emailList.add(new InternetAddress(emailString)); 1287 1288 } catch (AddressException e) { 1289 1290 throw new ParseException("Invalid \"contacts\" parameter: " + 1291 e.getMessage()); 1292 } 1293 } 1294 1295 metadata.setContacts(emailList); 1296 jsonObject.remove("contacts"); 1297 } 1298 1299 1300 // Find lang-tagged client_name params 1301 Map<LangTag,Object> matches = LangTagUtils.find("client_name", jsonObject); 1302 1303 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1304 1305 try { 1306 metadata.setName((String)entry.getValue(), entry.getKey()); 1307 1308 } catch (ClassCastException e) { 1309 1310 throw new ParseException("Invalid \"client_name\" (language tag) parameter"); 1311 } 1312 1313 removeMember(jsonObject, "client_name", entry.getKey()); 1314 } 1315 1316 1317 matches = LangTagUtils.find("logo_uri", jsonObject); 1318 1319 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1320 1321 try { 1322 metadata.setLogoURI(new URI((String)entry.getValue()), entry.getKey()); 1323 1324 } catch (Exception e) { 1325 1326 throw new ParseException("Invalid \"logo_uri\" (language tag) parameter"); 1327 } 1328 1329 removeMember(jsonObject, "logo_uri", entry.getKey()); 1330 } 1331 1332 1333 matches = LangTagUtils.find("client_uri", jsonObject); 1334 1335 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1336 1337 try { 1338 metadata.setURI(new URI((String)entry.getValue()), entry.getKey()); 1339 1340 1341 } catch (Exception e) { 1342 1343 throw new ParseException("Invalid \"client_uri\" (language tag) parameter"); 1344 } 1345 1346 removeMember(jsonObject, "client_uri", entry.getKey()); 1347 } 1348 1349 1350 matches = LangTagUtils.find("policy_uri", jsonObject); 1351 1352 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1353 1354 try { 1355 metadata.setPolicyURI(new URI((String)entry.getValue()), entry.getKey()); 1356 1357 } catch (Exception e) { 1358 1359 throw new ParseException("Invalid \"policy_uri\" (language tag) parameter"); 1360 } 1361 1362 removeMember(jsonObject, "policy_uri", entry.getKey()); 1363 } 1364 1365 1366 matches = LangTagUtils.find("tos_uri", jsonObject); 1367 1368 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 1369 1370 try { 1371 metadata.setTermsOfServiceURI(new URI((String)entry.getValue()), entry.getKey()); 1372 1373 } catch (Exception e) { 1374 1375 throw new ParseException("Invalid \"tos_uri\" (language tag) parameter"); 1376 } 1377 1378 removeMember(jsonObject, "tos_uri", entry.getKey()); 1379 } 1380 1381 1382 if (jsonObject.containsKey("token_endpoint_auth_method")) { 1383 metadata.setTokenEndpointAuthMethod(new ClientAuthenticationMethod( 1384 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_method"))); 1385 1386 jsonObject.remove("token_endpoint_auth_method"); 1387 } 1388 1389 1390 if (jsonObject.containsKey("token_endpoint_auth_signing_alg")) { 1391 metadata.setTokenEndpointAuthJWSAlg(new JWSAlgorithm( 1392 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_signing_alg"))); 1393 1394 jsonObject.remove("token_endpoint_auth_signing_alg"); 1395 } 1396 1397 1398 if (jsonObject.containsKey("jwks_uri")) { 1399 metadata.setJWKSetURI(JSONObjectUtils.getURI(jsonObject, "jwks_uri")); 1400 jsonObject.remove("jwks_uri"); 1401 } 1402 1403 if (jsonObject.containsKey("jwks")) { 1404 1405 try { 1406 metadata.setJWKSet(JWKSet.parse(JSONObjectUtils.getJSONObject(jsonObject, "jwks"))); 1407 1408 } catch (java.text.ParseException e) { 1409 throw new ParseException(e.getMessage(), e); 1410 } 1411 1412 jsonObject.remove("jwks"); 1413 } 1414 1415 if (jsonObject.containsKey("software_id")) { 1416 metadata.setSoftwareID(new SoftwareID(JSONObjectUtils.getString(jsonObject, "software_id"))); 1417 jsonObject.remove("software_id"); 1418 } 1419 1420 if (jsonObject.containsKey("software_version")) { 1421 metadata.setSoftwareVersion(new SoftwareVersion(JSONObjectUtils.getString(jsonObject, "software_version"))); 1422 jsonObject.remove("software_version"); 1423 } 1424 1425 // The remaining fields are custom 1426 metadata.customFields = jsonObject; 1427 1428 return metadata; 1429 } 1430 1431 1432 /** 1433 * Removes a JSON object member with the specified base name and 1434 * optional language tag. 1435 * 1436 * @param jsonObject The JSON object. Must not be {@code null}. 1437 * @param name The base member name. Must not be {@code null}. 1438 * @param langTag The language tag, {@code null} if none. 1439 */ 1440 private static void removeMember(final JSONObject jsonObject, final String name, final LangTag langTag) { 1441 1442 if (langTag == null) 1443 jsonObject.remove(name); 1444 else 1445 jsonObject.remove(name + "#" + langTag); 1446 } 1447}