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