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