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