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