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