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