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