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