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