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