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