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-08), 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("tls_client_certificate_bound_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 TLS client certificate bound access tokens. 209 */ 210 private boolean tlsClientCertificateBoundAccessTokens = 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 tlsClientCertificateBoundAccessTokens = metadata.tlsClientCertificateBoundAccessTokens; 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 * @param langTag The language tag, {@code null} if not specified. 629 * 630 * @return The logo URI, {@code null} if not specified. 631 */ 632 public URI getLogoURI(final LangTag langTag) { 633 634 return logoURIEntries.get(langTag); 635 } 636 637 638 /** 639 * Gets the client application logo entries. Corresponds to the 640 * {@code logo_uri} client metadata field. 641 * 642 * @return The logo URI entries, empty map if none. 643 */ 644 public Map<LangTag,URI> getLogoURIEntries() { 645 646 return logoURIEntries; 647 } 648 649 650 /** 651 * Sets the client application logo. Corresponds to the 652 * {@code logo_uri} client metadata field, with no language 653 * tag. 654 * 655 * @param logoURI The logo URI, {@code null} if not specified. 656 */ 657 public void setLogoURI(final URI logoURI) { 658 659 logoURIEntries.put(null, logoURI); 660 } 661 662 663 /** 664 * Sets the client application logo. Corresponds to the 665 * {@code logo_uri} client metadata field, with an optional 666 * language tag. 667 * 668 * @param logoURI The logo URI. Must not be {@code null}. 669 * @param langTag The language tag, {@code null} if not specified. 670 */ 671 public void setLogoURI(final URI logoURI, final LangTag langTag) { 672 673 logoURIEntries.put(langTag, logoURI); 674 } 675 676 677 /** 678 * Gets the client home page. Corresponds to the {@code client_uri} 679 * client metadata field, with no language tag. 680 * 681 * @return The client URI, {@code null} if not specified. 682 */ 683 public URI getURI() { 684 685 return getURI(null); 686 } 687 688 689 /** 690 * Gets the client home page. Corresponds to the {@code client_uri} 691 * client metadata field, with an optional language tag. 692 * 693 * @param langTag The language tag, {@code null} if not specified. 694 * 695 * @return The client URI, {@code null} if not specified. 696 */ 697 public URI getURI(final LangTag langTag) { 698 699 return uriEntries.get(langTag); 700 } 701 702 703 /** 704 * Gets the client home page entries. Corresponds to the 705 * {@code client_uri} client metadata field. 706 * 707 * @return The client URI entries, empty map if none. 708 */ 709 public Map<LangTag,URI> getURIEntries() { 710 711 return uriEntries; 712 } 713 714 715 /** 716 * Sets the client home page. Corresponds to the {@code client_uri} 717 * client metadata field, with no language tag. 718 * 719 * @param uri The client URI, {@code null} if not specified. 720 */ 721 public void setURI(final URI uri) { 722 723 uriEntries.put(null, uri); 724 } 725 726 727 /** 728 * Sets the client home page. Corresponds to the {@code client_uri} 729 * client metadata field, with an optional language tag. 730 * 731 * @param uri The URI. Must not be {@code null}. 732 * @param langTag The language tag, {@code null} if not specified. 733 */ 734 public void setURI(final URI uri, final LangTag langTag) { 735 736 uriEntries.put(langTag, uri); 737 } 738 739 740 /** 741 * Gets the client policy for use of end-user data. Corresponds to the 742 * {@code policy_uri} client metadata field, with no language 743 * tag. 744 * 745 * @return The policy URI, {@code null} if not specified. 746 */ 747 public URI getPolicyURI() { 748 749 return getPolicyURI(null); 750 } 751 752 753 /** 754 * Gets the client policy for use of end-user data. Corresponds to the 755 * {@code policy_uri} client metadata field, with an optional 756 * language tag. 757 * 758 * @param langTag The language tag, {@code null} if not specified. 759 * 760 * @return The policy URI, {@code null} if not specified. 761 */ 762 public URI getPolicyURI(final LangTag langTag) { 763 764 return policyURIEntries.get(langTag); 765 } 766 767 768 /** 769 * Gets the client policy entries for use of end-user data. 770 * Corresponds to the {@code policy_uri} client metadata field. 771 * 772 * @return The policy URI entries, empty map if none. 773 */ 774 public Map<LangTag,URI> getPolicyURIEntries() { 775 776 return policyURIEntries; 777 } 778 779 780 /** 781 * Sets the client policy for use of end-user data. Corresponds to the 782 * {@code policy_uri} client metadata field, with no language 783 * tag. 784 * 785 * @param policyURI The policy URI, {@code null} if not specified. 786 */ 787 public void setPolicyURI(final URI policyURI) { 788 789 policyURIEntries.put(null, policyURI); 790 } 791 792 793 /** 794 * Sets the client policy for use of end-user data. Corresponds to the 795 * {@code policy_uri} client metadata field, with an optional 796 * language tag. 797 * 798 * @param policyURI The policy URI. Must not be {@code null}. 799 * @param langTag The language tag, {@code null} if not specified. 800 */ 801 public void setPolicyURI(final URI policyURI, final LangTag langTag) { 802 803 policyURIEntries.put(langTag, policyURI); 804 } 805 806 807 /** 808 * Gets the client's terms of service. Corresponds to the 809 * {@code tos_uri} client metadata field, with no language 810 * tag. 811 * 812 * @return The terms of service URI, {@code null} if not specified. 813 */ 814 public URI getTermsOfServiceURI() { 815 816 return getTermsOfServiceURI(null); 817 } 818 819 820 /** 821 * Gets the client's terms of service. Corresponds to the 822 * {@code tos_uri} client metadata field, with an optional 823 * language tag. 824 * 825 * @param langTag The language tag, {@code null} if not specified. 826 * 827 * @return The terms of service URI, {@code null} if not specified. 828 */ 829 public URI getTermsOfServiceURI(final LangTag langTag) { 830 831 return tosURIEntries.get(langTag); 832 } 833 834 835 /** 836 * Gets the client's terms of service entries. Corresponds to the 837 * {@code tos_uri} client metadata field. 838 * 839 * @return The terms of service URI entries, empty map if none. 840 */ 841 public Map<LangTag,URI> getTermsOfServiceURIEntries() { 842 843 return tosURIEntries; 844 } 845 846 847 /** 848 * Sets the client's terms of service. Corresponds to the 849 * {@code tos_uri} client metadata field, with no language 850 * tag. 851 * 852 * @param tosURI The terms of service URI, {@code null} if not 853 * specified. 854 */ 855 public void setTermsOfServiceURI(final URI tosURI) { 856 857 tosURIEntries.put(null, tosURI); 858 } 859 860 861 /** 862 * Sets the client's terms of service. Corresponds to the 863 * {@code tos_uri} client metadata field, with an optional 864 * language tag. 865 * 866 * @param tosURI The terms of service URI. Must not be {@code null}. 867 * @param langTag The language tag, {@code null} if not specified. 868 */ 869 public void setTermsOfServiceURI(final URI tosURI, final LangTag langTag) { 870 871 tosURIEntries.put(langTag, tosURI); 872 } 873 874 875 /** 876 * Gets the Token endpoint authentication method. Corresponds to the 877 * {@code token_endpoint_auth_method} client metadata field. 878 * 879 * @return The Token endpoint authentication method, {@code null} if 880 * not specified. 881 */ 882 public ClientAuthenticationMethod getTokenEndpointAuthMethod() { 883 884 return authMethod; 885 } 886 887 888 /** 889 * Sets the Token endpoint authentication method. Corresponds to the 890 * {@code token_endpoint_auth_method} client metadata field. 891 * 892 * @param authMethod The Token endpoint authentication method, 893 * {@code null} if not specified. 894 */ 895 public void setTokenEndpointAuthMethod(final ClientAuthenticationMethod authMethod) { 896 897 this.authMethod = authMethod; 898 } 899 900 901 /** 902 * Gets the JSON Web Signature (JWS) algorithm required for 903 * {@code private_key_jwt} and {@code client_secret_jwt} 904 * authentication at the Token endpoint. Corresponds to the 905 * {@code token_endpoint_auth_signing_alg} client metadata field. 906 * 907 * @return The JWS algorithm, {@code null} if not specified. 908 */ 909 public JWSAlgorithm getTokenEndpointAuthJWSAlg() { 910 911 return authJWSAlg; 912 } 913 914 915 /** 916 * Sets the JSON Web Signature (JWS) algorithm required for 917 * {@code private_key_jwt} and {@code client_secret_jwt} 918 * authentication at the Token endpoint. Corresponds to the 919 * {@code token_endpoint_auth_signing_alg} client metadata field. 920 * 921 * @param authJWSAlg The JWS algorithm, {@code null} if not specified. 922 */ 923 public void setTokenEndpointAuthJWSAlg(final JWSAlgorithm authJWSAlg) { 924 925 this.authJWSAlg = authJWSAlg; 926 } 927 928 929 /** 930 * Gets the URI for this client's JSON Web Key (JWK) set containing 931 * key(s) that are used in signing requests to the server and key(s) 932 * for encrypting responses. Corresponds to the {@code jwks_uri} client 933 * metadata field. 934 * 935 * @return The JWK set URI, {@code null} if not specified. 936 */ 937 public URI getJWKSetURI() { 938 939 return jwkSetURI; 940 } 941 942 943 /** 944 * Sets the URI for this client's JSON Web Key (JWK) set containing 945 * key(s) that are used in signing requests to the server and key(s) 946 * for encrypting responses. Corresponds to the {@code jwks_uri} client 947 * metadata field. 948 * 949 * @param jwkSetURI The JWK set URI, {@code null} if not specified. 950 */ 951 public void setJWKSetURI(final URI jwkSetURI) { 952 953 this.jwkSetURI = jwkSetURI; 954 } 955 956 957 /** 958 * Gets this client's JSON Web Key (JWK) set containing key(s) that are 959 * used in signing requests to the server and key(s) for encrypting 960 * responses. Intended as an alternative to {@link #getJWKSetURI} for 961 * native clients. Corresponds to the {@code jwks} client metadata 962 * field. 963 * 964 * @return The JWK set, {@code null} if not specified. 965 */ 966 public JWKSet getJWKSet() { 967 968 return jwkSet; 969 } 970 971 972 /** 973 * Sets this client's JSON Web Key (JWK) set containing key(s) that are 974 * used in signing requests to the server and key(s) for encrypting 975 * responses. Intended as an alternative to {@link #getJWKSetURI} for 976 * native clients. Corresponds to the {@code jwks} client metadata 977 * field. 978 * 979 * @param jwkSet The JWK set, {@code null} if not specified. 980 */ 981 public void setJWKSet(final JWKSet jwkSet) { 982 983 this.jwkSet = jwkSet; 984 } 985 986 987 /** 988 * Gets the identifier for the OAuth 2.0 client software. Corresponds 989 * to the {@code software_id} client metadata field. 990 * 991 * @return The software identifier, {@code null} if not specified. 992 */ 993 public SoftwareID getSoftwareID() { 994 995 return softwareID; 996 } 997 998 999 /** 1000 * Sets the identifier for the OAuth 2.0 client software. Corresponds 1001 * to the {@code software_id} client metadata field. 1002 * 1003 * @param softwareID The software identifier, {@code null} if not 1004 * specified. 1005 */ 1006 public void setSoftwareID(final SoftwareID softwareID) { 1007 1008 this.softwareID = softwareID; 1009 } 1010 1011 1012 /** 1013 * Gets the version identifier for the OAuth 2.0 client software. 1014 * Corresponds to the {@code software_version} client metadata field. 1015 * 1016 * @return The version identifier, {@code null} if not specified. 1017 */ 1018 public SoftwareVersion getSoftwareVersion() { 1019 1020 return softwareVersion; 1021 } 1022 1023 1024 /** 1025 * Sets the version identifier for the OAuth 2.0 client software. 1026 * Corresponds to the {@code software_version} client metadata field. 1027 * 1028 * @param softwareVersion The version identifier, {@code null} if not 1029 * specified. 1030 */ 1031 public void setSoftwareVersion(final SoftwareVersion softwareVersion) { 1032 1033 this.softwareVersion = softwareVersion; 1034 } 1035 1036 1037 /** 1038 * Sets the preference for TLS client certificate bound access tokens. 1039 * Corresponds to the 1040 * {@code tls_client_certificate_bound_access_tokens} client metadata 1041 * field. 1042 * 1043 * @return {@code true} indicates a preference for TLS client 1044 * certificate bound access tokens, {@code false} if none. 1045 */ 1046 public boolean getTLSClientCertificateBoundAccessTokens() { 1047 1048 return tlsClientCertificateBoundAccessTokens; 1049 } 1050 1051 1052 /** 1053 * Gets the preference for TLS client certificate bound access tokens. 1054 * Corresponds to the 1055 * {@code tls_client_certificate_bound_access_tokens} client metadata 1056 * field. 1057 * 1058 * @param tlsClientCertBoundTokens {@code true} indicates a preference 1059 * for TLS client certificate bound 1060 * access tokens, {@code false} if 1061 * none. 1062 */ 1063 public void setTLSClientCertificateBoundAccessTokens(final boolean tlsClientCertBoundTokens) { 1064 1065 tlsClientCertificateBoundAccessTokens = tlsClientCertBoundTokens; 1066 } 1067 1068 1069 /** 1070 * Sets the preference for TLS client certificate bound access tokens. 1071 * Corresponds to the 1072 * {@code tls_client_certificate_bound_access_tokens} client metadata 1073 * field. 1074 * 1075 * @return {@code true} indicates a preference for TLS client 1076 * certificate bound access tokens, {@code false} if none. 1077 */ 1078 @Deprecated 1079 public boolean getMutualTLSSenderConstrainedAccessTokens() { 1080 1081 return tlsClientCertificateBoundAccessTokens; 1082 } 1083 1084 1085 /** 1086 * Gets the preference for TLS client certificate bound access tokens. 1087 * Corresponds to the 1088 * {@code tls_client_certificate_bound_access_tokens} client metadata 1089 * field. 1090 * 1091 * @param tlsSenderAccessTokens {@code true} indicates a preference for 1092 * TLS client certificate bound access 1093 * tokens, {@code false} if none. 1094 */ 1095 @Deprecated 1096 public void setMutualTLSSenderConstrainedAccessTokens(final boolean tlsSenderAccessTokens) { 1097 1098 tlsClientCertificateBoundAccessTokens = tlsSenderAccessTokens; 1099 } 1100 1101 1102 /** 1103 * Gets the expected subject distinguished name (DN) of the client 1104 * X.509 certificate in mutual TLS authentication. 1105 * 1106 * @return The expected subject distinguished name (DN) of the client 1107 * X.509 certificate, {@code null} if not specified. 1108 */ 1109 public String getTLSClientAuthSubjectDN() { 1110 1111 return tlsClientAuthSubjectDN; 1112 } 1113 1114 1115 /** 1116 * Sets the expected subject distinguished name (DN) of the client 1117 * X.509 certificate in mutual TLS authentication. 1118 * 1119 * @param subjectDN The expected subject distinguished name (DN) of the 1120 * client X.509 certificate, {@code null} if not 1121 * specified. 1122 */ 1123 public void setTLSClientAuthSubjectDN(final String subjectDN) { 1124 1125 this.tlsClientAuthSubjectDN = subjectDN; 1126 } 1127 1128 1129 /** 1130 * Gets the specified custom metadata field. 1131 * 1132 * @param name The field name. Must not be {@code null}. 1133 * 1134 * @return The field value, typically serialisable to a JSON entity, 1135 * {@code null} if none. 1136 */ 1137 public Object getCustomField(final String name) { 1138 1139 return customFields.get(name); 1140 } 1141 1142 1143 /** 1144 * Gets the custom metadata fields. 1145 * 1146 * @return The custom metadata fields, as a JSON object, empty object 1147 * if none. 1148 */ 1149 public JSONObject getCustomFields() { 1150 1151 return customFields; 1152 } 1153 1154 1155 /** 1156 * Sets the specified custom metadata field. 1157 * 1158 * @param name The field name. Must not be {@code null}. 1159 * @param value The field value. Should serialise to a JSON entity. 1160 */ 1161 public void setCustomField(final String name, final Object value) { 1162 1163 customFields.put(name, value); 1164 } 1165 1166 1167 /** 1168 * Sets the custom metadata fields. 1169 * 1170 * @param customFields The custom metadata fields, as a JSON object, 1171 * empty object if none. Must not be {@code null}. 1172 */ 1173 public void setCustomFields(final JSONObject customFields) { 1174 1175 if (customFields == null) 1176 throw new IllegalArgumentException("The custom fields JSON object must not be null"); 1177 1178 this.customFields = customFields; 1179 } 1180 1181 1182 /** 1183 * Applies the client metadata defaults where no values have been 1184 * specified. 1185 * 1186 * <ul> 1187 * <li>The response types default to {@code ["code"]}. 1188 * <li>The grant types default to {@code ["authorization_code"]}. 1189 * <li>The client authentication method defaults to 1190 * "client_secret_basic", unless the grant type is "implicit" 1191 * only. 1192 * </ul> 1193 */ 1194 public void applyDefaults() { 1195 1196 if (responseTypes == null) { 1197 responseTypes = new HashSet<>(); 1198 responseTypes.add(ResponseType.getDefault()); 1199 } 1200 1201 if (grantTypes == null) { 1202 grantTypes = new HashSet<>(); 1203 grantTypes.add(GrantType.AUTHORIZATION_CODE); 1204 } 1205 1206 if (authMethod == null) { 1207 1208 if (grantTypes.contains(GrantType.IMPLICIT) && grantTypes.size() == 1) { 1209 authMethod = ClientAuthenticationMethod.NONE; 1210 } else { 1211 authMethod = ClientAuthenticationMethod.getDefault(); 1212 } 1213 } 1214 } 1215 1216 1217 /** 1218 * Returns the JSON object representation of this client metadata, 1219 * including any custom fields. 1220 * 1221 * @return The JSON object. 1222 */ 1223 public JSONObject toJSONObject() { 1224 1225 return toJSONObject(true); 1226 } 1227 1228 1229 /** 1230 * Returns the JSON object representation of this client metadata. 1231 * 1232 * @param includeCustomFields {@code true} to include any custom 1233 * metadata fields, {@code false} to omit 1234 * them. 1235 * 1236 * @return The JSON object. 1237 */ 1238 public JSONObject toJSONObject(final boolean includeCustomFields) { 1239 1240 JSONObject o; 1241 1242 if (includeCustomFields) 1243 o = new JSONObject(customFields); 1244 else 1245 o = new JSONObject(); 1246 1247 1248 if (redirectURIs != null) { 1249 1250 JSONArray uriList = new JSONArray(); 1251 1252 for (URI uri: redirectURIs) 1253 uriList.add(uri.toString()); 1254 1255 o.put("redirect_uris", uriList); 1256 } 1257 1258 1259 if (scope != null) 1260 o.put("scope", scope.toString()); 1261 1262 1263 if (responseTypes != null) { 1264 1265 JSONArray rtList = new JSONArray(); 1266 1267 for (ResponseType rt: responseTypes) 1268 rtList.add(rt.toString()); 1269 1270 o.put("response_types", rtList); 1271 } 1272 1273 1274 if (grantTypes != null) { 1275 1276 JSONArray grantList = new JSONArray(); 1277 1278 for (GrantType grant: grantTypes) 1279 grantList.add(grant.toString()); 1280 1281 o.put("grant_types", grantList); 1282 } 1283 1284 1285 if (contacts != null) { 1286 o.put("contacts", contacts); 1287 } 1288 1289 1290 if (! nameEntries.isEmpty()) { 1291 1292 for (Map.Entry<LangTag,String> entry: nameEntries.entrySet()) { 1293 1294 LangTag langTag = entry.getKey(); 1295 String name = entry.getValue(); 1296 1297 if (name == null) 1298 continue; 1299 1300 if (langTag == null) 1301 o.put("client_name", entry.getValue()); 1302 else 1303 o.put("client_name#" + langTag, entry.getValue()); 1304 } 1305 } 1306 1307 1308 if (! logoURIEntries.isEmpty()) { 1309 1310 for (Map.Entry<LangTag,URI> entry: logoURIEntries.entrySet()) { 1311 1312 LangTag langTag = entry.getKey(); 1313 URI uri = entry.getValue(); 1314 1315 if (uri == null) 1316 continue; 1317 1318 if (langTag == null) 1319 o.put("logo_uri", entry.getValue().toString()); 1320 else 1321 o.put("logo_uri#" + langTag, entry.getValue().toString()); 1322 } 1323 } 1324 1325 1326 if (! uriEntries.isEmpty()) { 1327 1328 for (Map.Entry<LangTag,URI> entry: uriEntries.entrySet()) { 1329 1330 LangTag langTag = entry.getKey(); 1331 URI uri = entry.getValue(); 1332 1333 if (uri == null) 1334 continue; 1335 1336 if (langTag == null) 1337 o.put("client_uri", entry.getValue().toString()); 1338 else 1339 o.put("client_uri#" + langTag, entry.getValue().toString()); 1340 } 1341 } 1342 1343 1344 if (! policyURIEntries.isEmpty()) { 1345 1346 for (Map.Entry<LangTag,URI> entry: policyURIEntries.entrySet()) { 1347 1348 LangTag langTag = entry.getKey(); 1349 URI uri = entry.getValue(); 1350 1351 if (uri == null) 1352 continue; 1353 1354 if (langTag == null) 1355 o.put("policy_uri", entry.getValue().toString()); 1356 else 1357 o.put("policy_uri#" + langTag, entry.getValue().toString()); 1358 } 1359 } 1360 1361 1362 if (! tosURIEntries.isEmpty()) { 1363 1364 for (Map.Entry<LangTag,URI> entry: tosURIEntries.entrySet()) { 1365 1366 LangTag langTag = entry.getKey(); 1367 URI uri = entry.getValue(); 1368 1369 if (uri == null) 1370 continue; 1371 1372 if (langTag == null) 1373 o.put("tos_uri", entry.getValue().toString()); 1374 else 1375 o.put("tos_uri#" + langTag, entry.getValue().toString()); 1376 } 1377 } 1378 1379 1380 if (authMethod != null) 1381 o.put("token_endpoint_auth_method", authMethod.toString()); 1382 1383 1384 if (authJWSAlg != null) 1385 o.put("token_endpoint_auth_signing_alg", authJWSAlg.getName()); 1386 1387 1388 if (jwkSetURI != null) 1389 o.put("jwks_uri", jwkSetURI.toString()); 1390 1391 1392 if (jwkSet != null) 1393 o.put("jwks", jwkSet.toJSONObject(true)); // prevent private keys from leaking 1394 1395 1396 if (softwareID != null) 1397 o.put("software_id", softwareID.getValue()); 1398 1399 if (softwareVersion != null) 1400 o.put("software_version", softwareVersion.getValue()); 1401 1402 o.put("tls_client_certificate_bound_access_tokens", tlsClientCertificateBoundAccessTokens); 1403 1404 if (tlsClientAuthSubjectDN != null) 1405 o.put("tls_client_auth_subject_dn", tlsClientAuthSubjectDN); 1406 1407 return o; 1408 } 1409 1410 1411 @Override 1412 public String toString() { 1413 return toJSONObject().toJSONString(); 1414 } 1415 1416 1417 /** 1418 * Parses an client metadata instance from the specified JSON object. 1419 * 1420 * @param jsonObject The JSON object to parse. Must not be 1421 * {@code null}. 1422 * 1423 * @return The client metadata. 1424 * 1425 * @throws ParseException If the JSON object couldn't be parsed to a 1426 * client metadata instance. 1427 */ 1428 public static ClientMetadata parse(final JSONObject jsonObject) 1429 throws ParseException { 1430 1431 // Copy JSON object, then parse 1432 return parseFromModifiableJSONObject(new JSONObject(jsonObject)); 1433 } 1434 1435 1436 /** 1437 * Parses an client metadata instance from the specified JSON object. 1438 * 1439 * @param jsonObject The JSON object to parse, will be modified by 1440 * the parse routine. Must not be {@code null}. 1441 * 1442 * @return The client metadata. 1443 * 1444 * @throws ParseException If the JSON object couldn't be parsed to a 1445 * client metadata instance. 1446 */ 1447 private static ClientMetadata parseFromModifiableJSONObject(final JSONObject jsonObject) 1448 throws ParseException { 1449 1450 ClientMetadata metadata = new ClientMetadata(); 1451 1452 if (jsonObject.get("redirect_uris") != null) { 1453 1454 Set<URI> redirectURIs = new LinkedHashSet<>(); 1455 1456 for (String uriString: JSONObjectUtils.getStringArray(jsonObject, "redirect_uris")) { 1457 URI uri; 1458 try { 1459 uri = new URI(uriString); 1460 } catch (URISyntaxException e) { 1461 throw new ParseException("Invalid \"redirect_uris\" parameter: " + e.getMessage(), RegistrationError.INVALID_REDIRECT_URI.appendDescription(": " + e.getMessage())); 1462 } 1463 1464 if (uri.getFragment() != null) { 1465 String detail = "URI must not contain fragment"; 1466 throw new ParseException("Invalid \"redirect_uris\" parameter: " + detail, RegistrationError.INVALID_REDIRECT_URI.appendDescription(": " + detail)); 1467 } 1468 1469 redirectURIs.add(uri); 1470 } 1471 1472 metadata.setRedirectionURIs(redirectURIs); 1473 jsonObject.remove("redirect_uris"); 1474 } 1475 1476 try { 1477 1478 if (jsonObject.get("scope") != null) { 1479 metadata.setScope(Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"))); 1480 jsonObject.remove("scope"); 1481 } 1482 1483 1484 if (jsonObject.get("response_types") != null) { 1485 1486 Set<ResponseType> responseTypes = new LinkedHashSet<>(); 1487 1488 for (String rt : JSONObjectUtils.getStringArray(jsonObject, "response_types")) { 1489 1490 responseTypes.add(ResponseType.parse(rt)); 1491 } 1492 1493 metadata.setResponseTypes(responseTypes); 1494 jsonObject.remove("response_types"); 1495 } 1496 1497 1498 if (jsonObject.get("grant_types") != null) { 1499 1500 Set<GrantType> grantTypes = new LinkedHashSet<>(); 1501 1502 for (String grant : JSONObjectUtils.getStringArray(jsonObject, "grant_types")) { 1503 1504 grantTypes.add(GrantType.parse(grant)); 1505 } 1506 1507 metadata.setGrantTypes(grantTypes); 1508 jsonObject.remove("grant_types"); 1509 } 1510 1511 1512 if (jsonObject.get("contacts") != null) { 1513 metadata.setEmailContacts(JSONObjectUtils.getStringList(jsonObject, "contacts")); 1514 jsonObject.remove("contacts"); 1515 } 1516 1517 1518 // Find lang-tagged client_name params 1519 Map<LangTag, Object> matches = LangTagUtils.find("client_name", jsonObject); 1520 1521 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1522 1523 try { 1524 metadata.setName((String) entry.getValue(), entry.getKey()); 1525 1526 } catch (ClassCastException e) { 1527 1528 throw new ParseException("Invalid \"client_name\" (language tag) parameter"); 1529 } 1530 1531 removeMember(jsonObject, "client_name", entry.getKey()); 1532 } 1533 1534 1535 matches = LangTagUtils.find("logo_uri", jsonObject); 1536 1537 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1538 1539 if (entry.getValue() == null) continue; 1540 1541 try { 1542 metadata.setLogoURI(new URI((String) entry.getValue()), entry.getKey()); 1543 1544 } catch (Exception e) { 1545 1546 throw new ParseException("Invalid \"logo_uri\" (language tag) parameter"); 1547 } 1548 1549 removeMember(jsonObject, "logo_uri", entry.getKey()); 1550 } 1551 1552 1553 matches = LangTagUtils.find("client_uri", jsonObject); 1554 1555 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1556 1557 if (entry.getValue() == null) continue; 1558 1559 try { 1560 metadata.setURI(new URI((String) entry.getValue()), entry.getKey()); 1561 1562 1563 } catch (Exception e) { 1564 1565 throw new ParseException("Invalid \"client_uri\" (language tag) parameter"); 1566 } 1567 1568 removeMember(jsonObject, "client_uri", entry.getKey()); 1569 } 1570 1571 1572 matches = LangTagUtils.find("policy_uri", jsonObject); 1573 1574 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1575 1576 if (entry.getValue() == null) continue; 1577 1578 try { 1579 metadata.setPolicyURI(new URI((String) entry.getValue()), entry.getKey()); 1580 1581 } catch (Exception e) { 1582 1583 throw new ParseException("Invalid \"policy_uri\" (language tag) parameter"); 1584 } 1585 1586 removeMember(jsonObject, "policy_uri", entry.getKey()); 1587 } 1588 1589 1590 matches = LangTagUtils.find("tos_uri", jsonObject); 1591 1592 for (Map.Entry<LangTag, Object> entry : matches.entrySet()) { 1593 1594 if (entry.getValue() == null) continue; 1595 1596 try { 1597 metadata.setTermsOfServiceURI(new URI((String) entry.getValue()), entry.getKey()); 1598 1599 } catch (Exception e) { 1600 1601 throw new ParseException("Invalid \"tos_uri\" (language tag) parameter"); 1602 } 1603 1604 removeMember(jsonObject, "tos_uri", entry.getKey()); 1605 } 1606 1607 1608 if (jsonObject.get("token_endpoint_auth_method") != null) { 1609 metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.parse( 1610 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_method"))); 1611 1612 jsonObject.remove("token_endpoint_auth_method"); 1613 } 1614 1615 1616 if (jsonObject.get("token_endpoint_auth_signing_alg") != null) { 1617 metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.parse( 1618 JSONObjectUtils.getString(jsonObject, "token_endpoint_auth_signing_alg"))); 1619 1620 jsonObject.remove("token_endpoint_auth_signing_alg"); 1621 } 1622 1623 1624 if (jsonObject.get("jwks_uri") != null) { 1625 metadata.setJWKSetURI(JSONObjectUtils.getURI(jsonObject, "jwks_uri")); 1626 jsonObject.remove("jwks_uri"); 1627 } 1628 1629 if (jsonObject.get("jwks") != null) { 1630 1631 try { 1632 metadata.setJWKSet(JWKSet.parse(JSONObjectUtils.getJSONObject(jsonObject, "jwks"))); 1633 1634 } catch (java.text.ParseException e) { 1635 throw new ParseException(e.getMessage(), e); 1636 } 1637 1638 jsonObject.remove("jwks"); 1639 } 1640 1641 if (jsonObject.get("software_id") != null) { 1642 metadata.setSoftwareID(new SoftwareID(JSONObjectUtils.getString(jsonObject, "software_id"))); 1643 jsonObject.remove("software_id"); 1644 } 1645 1646 if (jsonObject.get("software_version") != null) { 1647 metadata.setSoftwareVersion(new SoftwareVersion(JSONObjectUtils.getString(jsonObject, "software_version"))); 1648 jsonObject.remove("software_version"); 1649 } 1650 1651 if (jsonObject.get("tls_client_certificate_bound_access_tokens") != null) { 1652 metadata.setTLSClientCertificateBoundAccessTokens(JSONObjectUtils.getBoolean(jsonObject, "tls_client_certificate_bound_access_tokens")); 1653 jsonObject.remove("tls_client_certificate_bound_access_tokens"); 1654 } 1655 1656 if (jsonObject.get("tls_client_auth_subject_dn") != null) { 1657 metadata.setTLSClientAuthSubjectDN(JSONObjectUtils.getString(jsonObject, "tls_client_auth_subject_dn")); 1658 jsonObject.remove("tls_client_auth_subject_dn"); 1659 } 1660 1661 } catch (ParseException e) { 1662 // Insert client_client_metadata error code so that it 1663 // can be reported back to the client if we have a 1664 // registration event 1665 throw new ParseException(e.getMessage(), RegistrationError.INVALID_CLIENT_METADATA.appendDescription(": " + e.getMessage()), e.getCause()); 1666 } 1667 1668 // The remaining fields are custom 1669 metadata.customFields = jsonObject; 1670 1671 return metadata; 1672 } 1673 1674 1675 /** 1676 * Removes a JSON object member with the specified base name and 1677 * optional language tag. 1678 * 1679 * @param jsonObject The JSON object. Must not be {@code null}. 1680 * @param name The base member name. Must not be {@code null}. 1681 * @param langTag The language tag, {@code null} if none. 1682 */ 1683 private static void removeMember(final JSONObject jsonObject, final String name, final LangTag langTag) { 1684 1685 if (langTag == null) 1686 jsonObject.remove(name); 1687 else 1688 jsonObject.remove(name + "#" + langTag); 1689 } 1690}