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. Valid redirection URIs must not 293 * contain a fragment. 294 */ 295 public void setRedirectionURIs(final Set<URI> redirectURIs) { 296 297 if (redirectURIs != null) { 298 // check URIs 299 for (URI uri: redirectURIs) { 300 if (uri == null) { 301 throw new IllegalArgumentException("The redirect_uri must not be null"); 302 } 303 if (uri.getFragment() != null) { 304 throw new IllegalArgumentException("The redirect_uri must not contain fragment"); 305 } 306 } 307 this.redirectURIs = redirectURIs; 308 } else { 309 this.redirectURIs = null; 310 } 311 } 312 313 314 /** 315 * Sets a single redirection URI for this client. Corresponds to the 316 * {@code redirect_uris} client metadata field. 317 * 318 * @param redirectURI The redirection URIs, {@code null} if not 319 * specified. A valid redirection URI must not 320 * contain a fragment. 321 */ 322 public void setRedirectionURI(final URI redirectURI) { 323 324 setRedirectionURIs(redirectURI != null ? Collections.singleton(redirectURI) : null); 325 } 326 327 328 /** 329 * Gets the scope values that the client can use when requesting access 330 * tokens. Corresponds to the {@code scope} client metadata field. 331 * 332 * @return The scope, {@code null} if not specified. 333 */ 334 public Scope getScope() { 335 336 return scope; 337 } 338 339 340 /** 341 * Checks if the scope matadata field is set and contains the specified 342 * scope value. 343 * 344 * @param scopeValue The scope value. Must not be {@code null}. 345 * 346 * @return {@code true} if the scope value is contained, else 347 * {@code false}. 348 */ 349 public boolean hasScopeValue(final Scope.Value scopeValue) { 350 351 return scope != null && scope.contains(scopeValue); 352 } 353 354 355 /** 356 * Sets the scope values that the client can use when requesting access 357 * tokens. Corresponds to the {@code scope} client metadata field. 358 * 359 * @param scope The scope, {@code null} if not specified. 360 */ 361 public void setScope(final Scope scope) { 362 363 this.scope = scope; 364 } 365 366 367 /** 368 * Gets the expected OAuth 2.0 response types. Corresponds to the 369 * {@code response_types} client metadata field. 370 * 371 * @return The response types, {@code null} if not specified. 372 */ 373 public Set<ResponseType> getResponseTypes() { 374 375 return responseTypes; 376 } 377 378 379 /** 380 * Sets the expected OAuth 2.0 response types. Corresponds to the 381 * {@code response_types} client metadata field. 382 * 383 * @param responseTypes The response types, {@code null} if not 384 * specified. 385 */ 386 public void setResponseTypes(final Set<ResponseType> responseTypes) { 387 388 this.responseTypes = responseTypes; 389 } 390 391 392 /** 393 * Gets the expected OAuth 2.0 grant types. Corresponds to the 394 * {@code grant_types} client metadata field. 395 * 396 * @return The grant types, {@code null} if not specified. 397 */ 398 public Set<GrantType> getGrantTypes() { 399 400 return grantTypes; 401 } 402 403 404 /** 405 * Sets the expected OAuth 2.0 grant types. Corresponds to the 406 * {@code grant_types} client metadata field. 407 * 408 * @param grantTypes The grant types, {@code null} if not specified. 409 */ 410 public void setGrantTypes(final Set<GrantType> grantTypes) { 411 412 this.grantTypes = grantTypes; 413 } 414 415 416 /** 417 * Gets the administrator contacts for the client. Corresponds to the 418 * {@code contacts} client metadata field. 419 * 420 * @return The administrator contacts, {@code null} if not specified. 421 */ 422 public List<InternetAddress> getContacts() { 423 424 return contacts; 425 } 426 427 428 /** 429 * Sets the administrator contacts for the client. Corresponds to the 430 * {@code contacts} client metadata field. 431 * 432 * @param contacts The administrator contacts, {@code null} if not 433 * specified. 434 */ 435 public void setContacts(final List<InternetAddress> contacts) { 436 437 this.contacts = contacts; 438 } 439 440 441 /** 442 * Gets the client name. Corresponds to the {@code client_name} client 443 * metadata field, with no language tag. 444 * 445 * @return The client name, {@code null} if not specified. 446 */ 447 public String getName() { 448 449 return getName(null); 450 } 451 452 453 /** 454 * Gets the client name. Corresponds to the {@code client_name} client 455 * metadata field, with an optional language tag. 456 * 457 * @param langTag The language tag of the entry, {@code null} to get 458 * the non-tagged entry. 459 * 460 * @return The client name, {@code null} if not specified. 461 */ 462 public String getName(final LangTag langTag) { 463 464 return nameEntries.get(langTag); 465 } 466 467 468 /** 469 * Gets the client name entries. Corresponds to the {@code client_name} 470 * client metadata field. 471 * 472 * @return The client name entries, empty map if none. 473 */ 474 public Map<LangTag,String> getNameEntries() { 475 476 return nameEntries; 477 } 478 479 480 /** 481 * Sets the client name. Corresponds to the {@code client_name} client 482 * metadata field, with no language tag. 483 * 484 * @param name The client name, {@code null} if not specified. 485 */ 486 public void setName(final String name) { 487 488 nameEntries.put(null, name); 489 } 490 491 492 /** 493 * Sets the client name. Corresponds to the {@code client_name} client 494 * metadata field, with an optional language tag. 495 * 496 * @param name The client name. Must not be {@code null}. 497 * @param langTag The language tag, {@code null} if not specified. 498 */ 499 public void setName(final String name, final LangTag langTag) { 500 501 nameEntries.put(langTag, name); 502 } 503 504 505 /** 506 * Gets the client application logo. Corresponds to the 507 * {@code logo_uri} client metadata field, with no language 508 * tag. 509 * 510 * @return The logo URI, {@code null} if not specified. 511 */ 512 public URI getLogoURI() { 513 514 return getLogoURI(null); 515 } 516 517 518 /** 519 * Gets the client application logo. Corresponds to the 520 * {@code logo_uri} client metadata field, with an optional 521 * language tag. 522 * 523 * @return The logo URI, {@code null} if not specified. 524 */ 525 public URI getLogoURI(final LangTag langTag) { 526 527 return logoURIEntries.get(langTag); 528 } 529 530 531 /** 532 * Gets the client application logo entries. Corresponds to the 533 * {@code logo_uri} client metadata field. 534 * 535 * @return The logo URI entries, empty map if none. 536 */ 537 public Map<LangTag,URI> getLogoURIEntries() { 538 539 return logoURIEntries; 540 } 541 542 543 /** 544 * Sets the client application logo. Corresponds to the 545 * {@code logo_uri} client metadata field, with no language 546 * tag. 547 * 548 * @param logoURI The logo URI, {@code null} if not specified. 549 */ 550 public void setLogoURI(final URI logoURI) { 551 552 logoURIEntries.put(null, logoURI); 553 } 554 555 556 /** 557 * Sets the client application logo. Corresponds to the 558 * {@code logo_uri} client metadata field, with an optional 559 * language tag. 560 * 561 * @param logoURI The logo URI. Must not be {@code null}. 562 * @param langTag The language tag, {@code null} if not specified. 563 */ 564 public void setLogoURI(final URI logoURI, final LangTag langTag) { 565 566 logoURIEntries.put(langTag, logoURI); 567 } 568 569 570 /** 571 * Gets the client home page. Corresponds to the {@code client_uri} 572 * client metadata field, with no language tag. 573 * 574 * @return The client URI, {@code null} if not specified. 575 */ 576 public URI getURI() { 577 578 return getURI(null); 579 } 580 581 582 /** 583 * Gets the client home page. Corresponds to the {@code client_uri} 584 * client metadata field, with an optional language tag. 585 * 586 * @return The client URI, {@code null} if not specified. 587 */ 588 public URI getURI(final LangTag langTag) { 589 590 return uriEntries.get(langTag); 591 } 592 593 594 /** 595 * Gets the client home page entries. Corresponds to the 596 * {@code client_uri} client metadata field. 597 * 598 * @return The client URI entries, empty map if none. 599 */ 600 public Map<LangTag,URI> getURIEntries() { 601 602 return uriEntries; 603 } 604 605 606 /** 607 * Sets the client home page. Corresponds to the {@code client_uri} 608 * client metadata field, with no language tag. 609 * 610 * @param uri The client URI, {@code null} if not specified. 611 */ 612 public void setURI(final URI uri) { 613 614 uriEntries.put(null, uri); 615 } 616 617 618 /** 619 * Sets the client home page. Corresponds to the {@code client_uri} 620 * client metadata field, with an optional language tag. 621 * 622 * @param uri The URI. Must not be {@code null}. 623 * @param langTag The language tag, {@code null} if not specified. 624 */ 625 public void setURI(final URI uri, final LangTag langTag) { 626 627 uriEntries.put(langTag, uri); 628 } 629 630 631 /** 632 * Gets the client policy for use of end-user data. Corresponds to the 633 * {@code policy_uri} client metadata field, with no language 634 * tag. 635 * 636 * @return The policy URI, {@code null} if not specified. 637 */ 638 public URI getPolicyURI() { 639 640 return getPolicyURI(null); 641 } 642 643 644 /** 645 * Gets the client policy for use of end-user data. Corresponds to the 646 * {@code policy_uri} client metadata field, with an optional 647 * language tag. 648 * 649 * @return The policy URI, {@code null} if not specified. 650 */ 651 public URI getPolicyURI(final LangTag langTag) { 652 653 return policyURIEntries.get(langTag); 654 } 655 656 657 /** 658 * Gets the client policy entries for use of end-user data. 659 * Corresponds to the {@code policy_uri} client metadata field. 660 * 661 * @return The policy URI entries, empty map if none. 662 */ 663 public Map<LangTag,URI> getPolicyURIEntries() { 664 665 return policyURIEntries; 666 } 667 668 669 /** 670 * Sets the client policy for use of end-user data. Corresponds to the 671 * {@code policy_uri} client metadata field, with no language 672 * tag. 673 * 674 * @param policyURI The policy URI, {@code null} if not specified. 675 */ 676 public void setPolicyURI(final URI policyURI) { 677 678 policyURIEntries.put(null, policyURI); 679 } 680 681 682 /** 683 * Sets the client policy for use of end-user data. Corresponds to the 684 * {@code policy_uri} client metadata field, with an optional 685 * language tag. 686 * 687 * @param policyURI The policy URI. Must not be {@code null}. 688 * @param langTag The language tag, {@code null} if not specified. 689 */ 690 public void setPolicyURI(final URI policyURI, final LangTag langTag) { 691 692 policyURIEntries.put(langTag, policyURI); 693 } 694 695 696 /** 697 * Gets the client's terms of service. Corresponds to the 698 * {@code tos_uri} client metadata field, with no language 699 * tag. 700 * 701 * @return The terms of service URI, {@code null} if not specified. 702 */ 703 public URI getTermsOfServiceURI() { 704 705 return getTermsOfServiceURI(null); 706 } 707 708 709 /** 710 * Gets the client's terms of service. Corresponds to the 711 * {@code tos_uri} client metadata field, with an optional 712 * language tag. 713 * 714 * @return The terms of service URI, {@code null} if not specified. 715 */ 716 public URI getTermsOfServiceURI(final LangTag langTag) { 717 718 return tosURIEntries.get(langTag); 719 } 720 721 722 /** 723 * Gets the client's terms of service entries. Corresponds to the 724 * {@code tos_uri} client metadata field. 725 * 726 * @return The terms of service URI entries, empty map if none. 727 */ 728 public Map<LangTag,URI> getTermsOfServiceURIEntries() { 729 730 return tosURIEntries; 731 } 732 733 734 /** 735 * Sets the client's terms of service. Corresponds to the 736 * {@code tos_uri} client metadata field, with no language 737 * tag. 738 * 739 * @param tosURI The terms of service URI, {@code null} if not 740 * specified. 741 */ 742 public void setTermsOfServiceURI(final URI tosURI) { 743 744 tosURIEntries.put(null, tosURI); 745 } 746 747 748 /** 749 * Sets the client's terms of service. Corresponds to the 750 * {@code tos_uri} client metadata field, with an optional 751 * language tag. 752 * 753 * @param tosURI The terms of service URI. Must not be {@code null}. 754 * @param langTag The language tag, {@code null} if not specified. 755 */ 756 public void setTermsOfServiceURI(final URI tosURI, final LangTag langTag) { 757 758 tosURIEntries.put(langTag, tosURI); 759 } 760 761 762 /** 763 * Gets the Token endpoint authentication method. Corresponds to the 764 * {@code token_endpoint_auth_method} client metadata field. 765 * 766 * @return The Token endpoint authentication method, {@code null} if 767 * not specified. 768 */ 769 public ClientAuthenticationMethod getTokenEndpointAuthMethod() { 770 771 return authMethod; 772 } 773 774 775 /** 776 * Sets the Token endpoint authentication method. Corresponds to the 777 * {@code token_endpoint_auth_method} client metadata field. 778 * 779 * @param authMethod The Token endpoint authentication method, 780 * {@code null} if not specified. 781 */ 782 public void setTokenEndpointAuthMethod(final ClientAuthenticationMethod authMethod) { 783 784 this.authMethod = authMethod; 785 } 786 787 788 /** 789 * Gets the JSON Web Signature (JWS) algorithm required for 790 * {@code private_key_jwt} and {@code client_secret_jwt} 791 * authentication at the Token endpoint. Corresponds to the 792 * {@code token_endpoint_auth_signing_alg} client metadata field. 793 * 794 * @return The JWS algorithm, {@code null} if not specified. 795 */ 796 public JWSAlgorithm getTokenEndpointAuthJWSAlg() { 797 798 return authJWSAlg; 799 } 800 801 802 /** 803 * Sets the JSON Web Signature (JWS) algorithm required for 804 * {@code private_key_jwt} and {@code client_secret_jwt} 805 * authentication at the Token endpoint. Corresponds to the 806 * {@code token_endpoint_auth_signing_alg} client metadata field. 807 * 808 * @param authJWSAlg The JWS algorithm, {@code null} if not specified. 809 */ 810 public void setTokenEndpointAuthJWSAlg(final JWSAlgorithm authJWSAlg) { 811 812 this.authJWSAlg = authJWSAlg; 813 } 814 815 816 /** 817 * Gets the URI for this client's JSON Web Key (JWK) set containing 818 * key(s) that are used in signing requests to the server and key(s) 819 * for encrypting responses. Corresponds to the {@code jwks_uri} client 820 * metadata field. 821 * 822 * @return The JWK set URI, {@code null} if not specified. 823 */ 824 public URI getJWKSetURI() { 825 826 return jwkSetURI; 827 } 828 829 830 /** 831 * Sets the URI for this client's JSON Web Key (JWK) set containing 832 * key(s) that are used in signing requests to the server and key(s) 833 * for encrypting responses. Corresponds to the {@code jwks_uri} client 834 * metadata field. 835 * 836 * @param jwkSetURI The JWK set URI, {@code null} if not specified. 837 */ 838 public void setJWKSetURI(final URI jwkSetURI) { 839 840 this.jwkSetURI = jwkSetURI; 841 } 842 843 844 /** 845 * Gets this client's JSON Web Key (JWK) set containing key(s) that are 846 * used in signing requests to the server and key(s) for encrypting 847 * responses. Intended as an alternative to {@link #getJWKSetURI} for 848 * native clients. Corresponds to the {@code jwks} client metadata 849 * field. 850 * 851 * @return The JWK set, {@code null} if not specified. 852 */ 853 public JWKSet getJWKSet() { 854 855 return jwkSet; 856 } 857 858 859 /** 860 * Sets this client's JSON Web Key (JWK) set containing key(s) that are 861 * used in signing requests to the server and key(s) for encrypting 862 * responses. Intended as an alternative to {@link #getJWKSetURI} for 863 * native clients. Corresponds to the {@code jwks} client metadata 864 * field. 865 * 866 * @param jwkSet The JWK set, {@code null} if not specified. 867 */ 868 public void setJWKSet(final JWKSet jwkSet) { 869 870 this.jwkSet = jwkSet; 871 } 872 873 874 /** 875 * Gets the identifier for the OAuth 2.0 client software. Corresponds 876 * to the {@code software_id} client metadata field. 877 * 878 * @return The software identifier, {@code null} if not specified. 879 */ 880 public SoftwareID getSoftwareID() { 881 882 return softwareID; 883 } 884 885 886 /** 887 * Sets the identifier for the OAuth 2.0 client software. Corresponds 888 * to the {@code software_id} client metadata field. 889 * 890 * @param softwareID The software identifier, {@code null} if not 891 * specified. 892 */ 893 public void setSoftwareID(final SoftwareID softwareID) { 894 895 this.softwareID = softwareID; 896 } 897 898 899 /** 900 * Gets the version identifier for the OAuth 2.0 client software. 901 * Corresponds to the {@code software_version} client metadata field. 902 * 903 * @return The version identifier, {@code null} if not specified. 904 */ 905 public SoftwareVersion getSoftwareVersion() { 906 907 return softwareVersion; 908 } 909 910 911 /** 912 * Sets the version identifier for the OAuth 2.0 client software. 913 * Corresponds to the {@code software_version} client metadata field. 914 * 915 * @param softwareVersion The version identifier, {@code null} if not 916 * specified. 917 */ 918 public void setSoftwareVersion(final SoftwareVersion softwareVersion) { 919 920 this.softwareVersion = softwareVersion; 921 } 922 923 924 /** 925 * Gets the specified custom metadata field. 926 * 927 * @param name The field name. Must not be {@code null}. 928 * 929 * @return The field value, typically serialisable to a JSON entity, 930 * {@code null} if none. 931 */ 932 public Object getCustomField(final String name) { 933 934 return customFields.get(name); 935 } 936 937 938 /** 939 * Gets the custom metadata fields. 940 * 941 * @return The custom metadata fields, as a JSON object, empty object 942 * if none. 943 */ 944 public JSONObject getCustomFields() { 945 946 return customFields; 947 } 948 949 950 /** 951 * Sets the specified custom metadata field. 952 * 953 * @param name The field name. Must not be {@code null}. 954 * @param value The field value. Should serialise to a JSON entity. 955 */ 956 public void setCustomField(final String name, final Object value) { 957 958 customFields.put(name, value); 959 } 960 961 962 /** 963 * Sets the custom metadata fields. 964 * 965 * @param customFields The custom metadata fields, as a JSON object, 966 * empty object if none. Must not be {@code null}. 967 */ 968 public void setCustomFields(final JSONObject customFields) { 969 970 if (customFields == null) 971 throw new IllegalArgumentException("The custom fields JSON object must not be null"); 972 973 this.customFields = customFields; 974 } 975 976 977 /** 978 * Applies the client metadata defaults where no values have been 979 * specified. 980 * 981 * <ul> 982 * <li>The response types default to {@code ["code"]}. 983 * <li>The grant types default to {@code ["authorization_code"]}. 984 * <li>The client authentication method defaults to 985 * "client_secret_basic", unless the grant type is "implicit" 986 * only. 987 * </ul> 988 */ 989 public void applyDefaults() { 990 991 if (responseTypes == null) { 992 responseTypes = new HashSet<>(); 993 responseTypes.add(ResponseType.getDefault()); 994 } 995 996 if (grantTypes == null) { 997 grantTypes = new HashSet<>(); 998 grantTypes.add(GrantType.AUTHORIZATION_CODE); 999 } 1000 1001 if (authMethod == null) { 1002 1003 if (grantTypes.contains(GrantType.IMPLICIT) && grantTypes.size() == 1) { 1004 authMethod = ClientAuthenticationMethod.NONE; 1005 } else { 1006 authMethod = ClientAuthenticationMethod.getDefault(); 1007 } 1008 } 1009 } 1010 1011 1012 /** 1013 * Returns the JSON object representation of this client metadata, 1014 * including any custom fields. 1015 * 1016 * @return The JSON object. 1017 */ 1018 public JSONObject toJSONObject() { 1019 1020 return toJSONObject(true); 1021 } 1022 1023 1024 /** 1025 * Returns the JSON object representation of this client metadata. 1026 * 1027 * @param includeCustomFields {@code true} to include any custom 1028 * metadata fields, {@code false} to omit 1029 * them. 1030 * 1031 * @return The JSON object. 1032 */ 1033 public JSONObject toJSONObject(final boolean includeCustomFields) { 1034 1035 JSONObject o; 1036 1037 if (includeCustomFields) 1038 o = new JSONObject(customFields); 1039 else 1040 o = new JSONObject(); 1041 1042 1043 if (redirectURIs != null) { 1044 1045 JSONArray uriList = new JSONArray(); 1046 1047 for (URI uri: redirectURIs) 1048 uriList.add(uri.toString()); 1049 1050 o.put("redirect_uris", uriList); 1051 } 1052 1053 1054 if (scope != null) 1055 o.put("scope", scope.toString()); 1056 1057 1058 if (responseTypes != null) { 1059 1060 JSONArray rtList = new JSONArray(); 1061 1062 for (ResponseType rt: responseTypes) 1063 rtList.add(rt.toString()); 1064 1065 o.put("response_types", rtList); 1066 } 1067 1068 1069 if (grantTypes != null) { 1070 1071 JSONArray grantList = new JSONArray(); 1072 1073 for (GrantType grant: grantTypes) 1074 grantList.add(grant.toString()); 1075 1076 o.put("grant_types", grantList); 1077 } 1078 1079 1080 if (contacts != null) { 1081 1082 JSONArray contactList = new JSONArray(); 1083 1084 for (InternetAddress email: contacts) 1085 contactList.add(email.toString()); 1086 1087 o.put("contacts", contactList); 1088 } 1089 1090 1091 if (! nameEntries.isEmpty()) { 1092 1093 for (Map.Entry<LangTag,String> entry: nameEntries.entrySet()) { 1094 1095 LangTag langTag = entry.getKey(); 1096 String name = entry.getValue(); 1097 1098 if (name == null) 1099 continue; 1100 1101 if (langTag == null) 1102 o.put("client_name", entry.getValue()); 1103 else 1104 o.put("client_name#" + langTag, entry.getValue()); 1105 } 1106 } 1107 1108 1109 if (! logoURIEntries.isEmpty()) { 1110 1111 for (Map.Entry<LangTag,URI> entry: logoURIEntries.entrySet()) { 1112 1113 LangTag langTag = entry.getKey(); 1114 URI uri = entry.getValue(); 1115 1116 if (uri == null) 1117 continue; 1118 1119 if (langTag == null) 1120 o.put("logo_uri", entry.getValue().toString()); 1121 else 1122 o.put("logo_uri#" + langTag, entry.getValue().toString()); 1123 } 1124 } 1125 1126 1127 if (! uriEntries.isEmpty()) { 1128 1129 for (Map.Entry<LangTag,URI> entry: uriEntries.entrySet()) { 1130 1131 LangTag langTag = entry.getKey(); 1132 URI uri = entry.getValue(); 1133 1134 if (uri == null) 1135 continue; 1136 1137 if (langTag == null) 1138 o.put("client_uri", entry.getValue().toString()); 1139 else 1140 o.put("client_uri#" + langTag, entry.getValue().toString()); 1141 } 1142 } 1143 1144 1145 if (! policyURIEntries.isEmpty()) { 1146 1147 for (Map.Entry<LangTag,URI> entry: policyURIEntries.entrySet()) { 1148 1149 LangTag langTag = entry.getKey(); 1150 URI uri = entry.getValue(); 1151 1152 if (uri == null) 1153 continue; 1154 1155 if (langTag == null) 1156 o.put("policy_uri", entry.getValue().toString()); 1157 else 1158 o.put("policy_uri#" + langTag, entry.getValue().toString()); 1159 } 1160 } 1161 1162 1163 if (! tosURIEntries.isEmpty()) { 1164 1165 for (Map.Entry<LangTag,URI> entry: tosURIEntries.entrySet()) { 1166 1167 LangTag langTag = entry.getKey(); 1168 URI uri = entry.getValue(); 1169 1170 if (uri == null) 1171 continue; 1172 1173 if (langTag == null) 1174 o.put("tos_uri", entry.getValue().toString()); 1175 else 1176 o.put("tos_uri#" + langTag, entry.getValue().toString()); 1177 } 1178 } 1179 1180 1181 if (authMethod != null) 1182 o.put("token_endpoint_auth_method", authMethod.toString()); 1183 1184 1185 if (authJWSAlg != null) 1186 o.put("token_endpoint_auth_signing_alg", authJWSAlg.getName()); 1187 1188 1189 if (jwkSetURI != null) 1190 o.put("jwks_uri", jwkSetURI.toString()); 1191 1192 1193 if (jwkSet != null) 1194 o.put("jwks", jwkSet.toJSONObject(true)); // prevent private keys from leaking 1195 1196 1197 if (softwareID != null) 1198 o.put("software_id", softwareID.getValue()); 1199 1200 if (softwareVersion != null) 1201 o.put("software_version", softwareVersion.getValue()); 1202 1203 return o; 1204 } 1205 1206 1207 /** 1208 * Parses an client metadata instance from the specified JSON object. 1209 * 1210 * @param jsonObject The JSON object to parse. Must not be 1211 * {@code null}. 1212 * 1213 * @return The client metadata. 1214 * 1215 * @throws ParseException If the JSON object couldn't be parsed to a 1216 * client metadata instance. 1217 */ 1218 public static ClientMetadata parse(final JSONObject jsonObject) 1219 throws ParseException { 1220 1221 // Copy JSON object, then parse 1222 return parseFromModifiableJSONObject(new JSONObject(jsonObject)); 1223 } 1224 1225 1226 /** 1227 * Parses an client metadata instance from the specified JSON object. 1228 * 1229 * @param jsonObject The JSON object to parse, will be modified by 1230 * the parse routine. Must not be {@code null}. 1231 * 1232 * @return The client metadata. 1233 * 1234 * @throws ParseException If the JSON object couldn't be parsed to a 1235 * client metadata instance. 1236 */ 1237 private static ClientMetadata parseFromModifiableJSONObject(final JSONObject jsonObject) 1238 throws ParseException { 1239 1240 ClientMetadata metadata = new ClientMetadata(); 1241 1242 if (jsonObject.containsKey("redirect_uris")) { 1243 1244 Set<URI> redirectURIs = new LinkedHashSet<>(); 1245 1246 for (String uriString: JSONObjectUtils.getStringArray(jsonObject, "redirect_uris")) { 1247 URI uri; 1248 try { 1249 uri = new URI(uriString); 1250 } catch (URISyntaxException e) { 1251 throw new ParseException("Invalid \"redirect_uris\" parameter: " + e.getMessage(), RegistrationError.INVALID_REDIRECT_URI.appendDescription(": " + e.getMessage())); 1252 } 1253 1254 if (uri.getFragment() != null) { 1255 String detail = "URI must not contain fragment"; 1256 throw new ParseException("Invalid \"redirect_uris\" parameter: " + detail, RegistrationError.INVALID_REDIRECT_URI.appendDescription(": " + detail)); 1257 } 1258 1259 redirectURIs.add(uri); 1260 } 1261 1262 metadata.setRedirectionURIs(redirectURIs); 1263 jsonObject.remove("redirect_uris"); 1264 } 1265 1266 try { 1267 1268 if (jsonObject.containsKey("scope")) { 1269 metadata.setScope(Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"))); 1270 jsonObject.remove("scope"); 1271 } 1272 1273 1274 if (jsonObject.containsKey("response_types")) { 1275 1276 Set<ResponseType> responseTypes = new LinkedHashSet<>(); 1277 1278 for (String rt : JSONObjectUtils.getStringArray(jsonObject, "response_types")) { 1279 1280 responseTypes.add(ResponseType.parse(rt)); 1281 } 1282 1283 metadata.setResponseTypes(responseTypes); 1284 jsonObject.remove("response_types"); 1285 } 1286 1287 1288 if (jsonObject.containsKey("grant_types")) { 1289 1290 Set<GrantType> grantTypes = new LinkedHashSet<>(); 1291 1292 for (String grant : JSONObjectUtils.getStringArray(jsonObject, "grant_types")) { 1293 1294 grantTypes.add(GrantType.parse(grant)); 1295 } 1296 1297 metadata.setGrantTypes(grantTypes); 1298 jsonObject.remove("grant_types"); 1299 } 1300 1301 1302 if (jsonObject.containsKey("contacts")) { 1303 1304 List<InternetAddress> emailList = new LinkedList<>(); 1305 1306 for (String emailString : JSONObjectUtils.getStringArray(jsonObject, "contacts")) { 1307 1308 try { 1309 emailList.add(new InternetAddress(emailString)); 1310 1311 } catch (AddressException e) { 1312 1313 throw new ParseException("Invalid \"contacts\" parameter: " + 1314 e.getMessage()); 1315 } 1316 } 1317 1318 metadata.setContacts(emailList); 1319 jsonObject.remove("contacts"); 1320 } 1321 1322 1323 // Find lang-tagged client_name params 1324 Map<LangTag, Object> matches = LangTagUtils.find("client_name", jsonObject); 1325 1326 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1327 1328 try { 1329 metadata.setName((String) entry.getValue(), entry.getKey()); 1330 1331 } catch (ClassCastException e) { 1332 1333 throw new ParseException("Invalid \"client_name\" (language tag) parameter"); 1334 } 1335 1336 removeMember(jsonObject, "client_name", entry.getKey()); 1337 } 1338 1339 1340 matches = LangTagUtils.find("logo_uri", jsonObject); 1341 1342 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1343 1344 try { 1345 metadata.setLogoURI(new URI((String) entry.getValue()), entry.getKey()); 1346 1347 } catch (Exception e) { 1348 1349 throw new ParseException("Invalid \"logo_uri\" (language tag) parameter"); 1350 } 1351 1352 removeMember(jsonObject, "logo_uri", entry.getKey()); 1353 } 1354 1355 1356 matches = LangTagUtils.find("client_uri", jsonObject); 1357 1358 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1359 1360 try { 1361 metadata.setURI(new URI((String) entry.getValue()), entry.getKey()); 1362 1363 1364 } catch (Exception e) { 1365 1366 throw new ParseException("Invalid \"client_uri\" (language tag) parameter"); 1367 } 1368 1369 removeMember(jsonObject, "client_uri", entry.getKey()); 1370 } 1371 1372 1373 matches = LangTagUtils.find("policy_uri", jsonObject); 1374 1375 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1376 1377 try { 1378 metadata.setPolicyURI(new URI((String) entry.getValue()), entry.getKey()); 1379 1380 } catch (Exception e) { 1381 1382 throw new ParseException("Invalid \"policy_uri\" (language tag) parameter"); 1383 } 1384 1385 removeMember(jsonObject, "policy_uri", entry.getKey()); 1386 } 1387 1388 1389 matches = LangTagUtils.find("tos_uri", jsonObject); 1390 1391 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1392 1393 try { 1394 metadata.setTermsOfServiceURI(new URI((String) entry.getValue()), entry.getKey()); 1395 1396 } catch (Exception e) { 1397 1398 throw new ParseException("Invalid \"tos_uri\" (language tag) parameter"); 1399 } 1400 1401 removeMember(jsonObject, "tos_uri", entry.getKey()); 1402 } 1403 1404 1405 if (jsonObject.containsKey("token_endpoint_auth_method")) { 1406 metadata.setTokenEndpointAuthMethod(new ClientAuthenticationMethod( 1407 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_method"))); 1408 1409 jsonObject.remove("token_endpoint_auth_method"); 1410 } 1411 1412 1413 if (jsonObject.containsKey("token_endpoint_auth_signing_alg")) { 1414 metadata.setTokenEndpointAuthJWSAlg(new JWSAlgorithm( 1415 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_signing_alg"))); 1416 1417 jsonObject.remove("token_endpoint_auth_signing_alg"); 1418 } 1419 1420 1421 if (jsonObject.containsKey("jwks_uri")) { 1422 metadata.setJWKSetURI(JSONObjectUtils.getURI(jsonObject, "jwks_uri")); 1423 jsonObject.remove("jwks_uri"); 1424 } 1425 1426 if (jsonObject.containsKey("jwks")) { 1427 1428 try { 1429 metadata.setJWKSet(JWKSet.parse(JSONObjectUtils.getJSONObject(jsonObject, "jwks"))); 1430 1431 } catch (java.text.ParseException e) { 1432 throw new ParseException(e.getMessage(), e); 1433 } 1434 1435 jsonObject.remove("jwks"); 1436 } 1437 1438 if (jsonObject.containsKey("software_id")) { 1439 metadata.setSoftwareID(new SoftwareID(JSONObjectUtils.getString(jsonObject, "software_id"))); 1440 jsonObject.remove("software_id"); 1441 } 1442 1443 if (jsonObject.containsKey("software_version")) { 1444 metadata.setSoftwareVersion(new SoftwareVersion(JSONObjectUtils.getString(jsonObject, "software_version"))); 1445 jsonObject.remove("software_version"); 1446 } 1447 1448 } catch (ParseException e) { 1449 // Insert client_client_metadata error code so that it 1450 // can be reported back to the client if we have a 1451 // registration event 1452 throw new ParseException(e.getMessage(), RegistrationError.INVALID_CLIENT_METADATA.appendDescription(": " + e.getMessage()), e.getCause()); 1453 } 1454 1455 // The remaining fields are custom 1456 metadata.customFields = jsonObject; 1457 1458 return metadata; 1459 } 1460 1461 1462 /** 1463 * Removes a JSON object member with the specified base name and 1464 * optional language tag. 1465 * 1466 * @param jsonObject The JSON object. Must not be {@code null}. 1467 * @param name The base member name. Must not be {@code null}. 1468 * @param langTag The language tag, {@code null} if none. 1469 */ 1470 private static void removeMember(final JSONObject jsonObject, final String name, final LangTag langTag) { 1471 1472 if (langTag == null) 1473 jsonObject.remove(name); 1474 else 1475 jsonObject.remove(name + "#" + langTag); 1476 } 1477}