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