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