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