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