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