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