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