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