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