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