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