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