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        static {
078                Set<String> p = new HashSet<>();
079
080                p.add("redirect_uris");
081                p.add("scope");
082                p.add("response_types");
083                p.add("grant_types");
084                p.add("contacts");
085                p.add("client_name");
086                p.add("logo_uri");
087                p.add("client_uri");
088                p.add("policy_uri");
089                p.add("tos_uri");
090                p.add("token_endpoint_auth_method");
091                p.add("token_endpoint_auth_signing_alg");
092                p.add("jwks_uri");
093                p.add("jwks");
094                p.add("software_id");
095                p.add("software_version");
096
097                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
098        }
099        
100        
101        /**
102         * Redirect URIs.
103         */
104        private Set<URI> redirectURIs;
105
106
107        /**
108         * The client OAuth 2.0 scope.
109         */
110        private Scope scope;
111
112
113        /**
114         * The expected OAuth 2.0 response types.
115         */
116        private Set<ResponseType> responseTypes;
117
118
119        /**
120         * The expected OAuth 2.0 grant types.
121         */
122        private Set<GrantType> grantTypes;
123
124
125        /**
126         * Administrator email contacts for the client.
127         */
128        private List<String> contacts;
129
130
131        /**
132         * The client name.
133         */
134        private final Map<LangTag,String> nameEntries;
135
136
137        /**
138         * The client application logo.
139         */
140        private final Map<LangTag,URI> logoURIEntries;
141
142
143        /**
144         * The client URI entries.
145         */
146        private final Map<LangTag,URI> uriEntries;
147
148
149        /**
150         * The client policy for use of end-user data.
151         */
152        private Map<LangTag,URI> policyURIEntries;
153
154
155        /**
156         * The client terms of service.
157         */
158        private final Map<LangTag,URI> tosURIEntries;
159
160
161        /**
162         * Token endpoint authentication method.
163         */
164        private ClientAuthenticationMethod authMethod;
165
166
167        /**
168         * The JSON Web Signature (JWS) algorithm required for
169         * {@code private_key_jwt} and {@code client_secret_jwt}
170         * authentication at the Token endpoint.
171         */
172        private JWSAlgorithm authJWSAlg;
173
174
175        /**
176         * URI for this client's JSON Web Key (JWK) set containing key(s) that
177         * are used in signing requests to the server and key(s) for encrypting
178         * responses.
179         */
180        private URI jwkSetURI;
181
182
183        /**
184         * Client's JSON Web Key (JWK) set containing key(s) that are used in
185         * signing requests to the server and key(s) for encrypting responses.
186         * Intended as an alternative to {@link #jwkSetURI} for native clients.
187         */
188        private JWKSet jwkSet;
189
190
191        /**
192         * Identifier for the OAuth 2.0 client software.
193         */
194        private SoftwareID softwareID;
195
196
197        /**
198         * Version identifier for the OAuth 2.0 client software.
199         */
200        private SoftwareVersion softwareVersion;
201
202
203        /**
204         * The custom metadata fields.
205         */
206        private JSONObject customFields;
207
208
209        /**
210         * Creates a new OAuth 2.0 client metadata instance.
211         */
212        public ClientMetadata() {
213
214                nameEntries = new HashMap<>();
215                logoURIEntries = new HashMap<>();
216                uriEntries = new HashMap<>();
217                policyURIEntries = new HashMap<>();
218                policyURIEntries = new HashMap<>();
219                tosURIEntries = new HashMap<>();
220                customFields = new JSONObject();
221        }
222
223
224        /**
225         * Creates a shallow copy of the specified OAuth 2.0 client metadata
226         * instance.
227         *
228         * @param metadata The client metadata to copy. Must not be
229         *                 {@code null}.
230         */
231        public ClientMetadata(final ClientMetadata metadata) {
232
233                redirectURIs = metadata.redirectURIs;
234                scope = metadata.scope;
235                responseTypes = metadata.responseTypes;
236                grantTypes = metadata.grantTypes;
237                contacts = metadata.contacts;
238                nameEntries = metadata.nameEntries;
239                logoURIEntries = metadata.logoURIEntries;
240                uriEntries = metadata.uriEntries;
241                policyURIEntries = metadata.policyURIEntries;
242                tosURIEntries = metadata.tosURIEntries;
243                authMethod = metadata.authMethod;
244                authJWSAlg = metadata.authJWSAlg;
245                jwkSetURI = metadata.jwkSetURI;
246                jwkSet = metadata.getJWKSet();
247                softwareID = metadata.softwareID;
248                softwareVersion = metadata.softwareVersion;
249                customFields = metadata.customFields;
250        }
251
252
253        /**
254         * Gets the registered (standard) OAuth 2.0 client metadata parameter
255         * names.
256         *
257         * @return The registered parameter names, as an unmodifiable set.
258         */
259        public static Set<String> getRegisteredParameterNames() {
260
261                return REGISTERED_PARAMETER_NAMES;
262        }
263
264
265        /**
266         * Gets the redirection URIs for this client. Corresponds to the
267         * {@code redirect_uris} client metadata field.
268         *
269         * @return The redirection URIs, {@code null} if not specified.
270         */
271        public Set<URI> getRedirectionURIs() {
272
273                return redirectURIs;
274        }
275
276
277        /**
278         * Gets the redirection URIs for this client as strings. Corresponds to
279         * the {@code redirect_uris} client metadata field.
280         *
281         * <p>This short-hand method is intended to enable string-based URI
282         * comparison.
283         *
284         * @return The redirection URIs as strings, {@code null} if not
285         *         specified.
286         */
287        public Set<String> getRedirectionURIStrings() {
288
289                if (redirectURIs == null)
290                        return null;
291
292                Set<String> uriStrings = new HashSet<>();
293
294                for (URI uri: redirectURIs)
295                        uriStrings.add(uri.toString());
296
297                return uriStrings;
298        }
299
300
301        /**
302         * Sets the redirection URIs for this client. Corresponds to the
303         * {@code redirect_uris} client metadata field.
304         *
305         * @param redirectURIs The redirection URIs, {@code null} if not
306         *                     specified. Valid redirection URIs must not
307         *                     contain a fragment.
308         */
309        public void setRedirectionURIs(final Set<URI> redirectURIs) {
310
311                if (redirectURIs != null) {
312                        // check URIs
313                        for (URI uri: redirectURIs) {
314                                if (uri == null) {
315                                        throw new IllegalArgumentException("The redirect_uri must not be null");
316                                }
317                                if (uri.getFragment() != null) {
318                                        throw new IllegalArgumentException("The redirect_uri must not contain fragment");
319                                }
320                        }
321                        this.redirectURIs = redirectURIs;
322                } else {
323                        this.redirectURIs = null;
324                }
325        }
326
327
328        /**
329         * Sets a single redirection URI for this client. Corresponds to the
330         * {@code redirect_uris} client metadata field.
331         *
332         * @param redirectURI The redirection URIs, {@code null} if not
333         *                    specified. A valid redirection URI must not
334         *                    contain a fragment.
335         */
336        public void setRedirectionURI(final URI redirectURI) {
337
338                setRedirectionURIs(redirectURI != null ? Collections.singleton(redirectURI) : null);
339        }
340
341
342        /**
343         * Gets the scope values that the client can use when requesting access
344         * tokens. Corresponds to the {@code scope} client metadata field.
345         *
346         * @return The scope, {@code null} if not specified.
347         */
348        public Scope getScope() {
349
350                return scope;
351        }
352
353
354        /**
355         * Checks if the scope matadata field is set and contains the specified
356         * scope value.
357         *
358         * @param scopeValue The scope value. Must not be {@code null}.
359         *
360         * @return {@code true} if the scope value is contained, else
361         *         {@code false}.
362         */
363        public boolean hasScopeValue(final Scope.Value scopeValue) {
364
365                return scope != null && scope.contains(scopeValue);
366        }
367
368
369        /**
370         * Sets the scope values that the client can use when requesting access
371         * tokens. Corresponds to the {@code scope} client metadata field.
372         *
373         * @param scope The scope, {@code null} if not specified.
374         */
375        public void setScope(final Scope scope) {
376
377                this.scope = scope;
378        }
379
380
381        /**
382         * Gets the expected OAuth 2.0 response types. Corresponds to the
383         * {@code response_types} client metadata field.
384         *
385         * @return The response types, {@code null} if not specified.
386         */
387        public Set<ResponseType> getResponseTypes() {
388
389                return responseTypes;
390        }
391
392
393        /**
394         * Sets the expected OAuth 2.0 response types. Corresponds to the
395         * {@code response_types} client metadata field.
396         *
397         * @param responseTypes The response types, {@code null} if not
398         *                      specified.
399         */
400        public void setResponseTypes(final Set<ResponseType> responseTypes) {
401
402                this.responseTypes = responseTypes;
403        }
404
405
406        /**
407         * Gets the expected OAuth 2.0 grant types. Corresponds to the
408         * {@code grant_types} client metadata field.
409         *
410         * @return The grant types, {@code null} if not specified.
411         */
412        public Set<GrantType> getGrantTypes() {
413
414                return grantTypes;
415        }
416
417
418        /**
419         * Sets the expected OAuth 2.0 grant types. Corresponds to the
420         * {@code grant_types} client metadata field.
421         *
422         * @param grantTypes The grant types, {@code null} if not specified.
423         */
424        public void setGrantTypes(final Set<GrantType> grantTypes) {
425
426                this.grantTypes = grantTypes;
427        }
428
429
430        /**
431         * Gets the administrator email contacts for the client. Corresponds to
432         * the {@code contacts} client metadata field.
433         *
434         * <p>Use {@link #getEmailContacts()} instead.
435         *
436         * @return The administrator email contacts, {@code null} if not
437         *         specified.
438         */
439        @Deprecated
440        public List<InternetAddress> getContacts() {
441
442                if (contacts == null)
443                        return null;
444                
445                List<InternetAddress> addresses = new LinkedList<>();
446                for (String s: contacts) {
447                        if (s == null) continue;
448                        try {
449                                addresses.add(new InternetAddress(s, false));
450                        } catch (AddressException e) {
451                                // ignore
452                        }
453                }
454                return addresses;
455        }
456
457
458        /**
459         * Sets the administrator email contacts for the client. Corresponds to
460         * the {@code contacts} client metadata field.
461         *
462         * <p>Use {@link #setEmailContacts(List)} instead.
463         *
464         * @param contacts The administrator email contacts, {@code null} if
465         *                 not specified.
466         */
467        @Deprecated
468        public void setContacts(final List<InternetAddress> contacts) {
469
470                if (contacts == null) {
471                        this.contacts = null;
472                        return;
473                }
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(ClientAuthenticationMethod.parse(
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(JWSAlgorithm.parse(
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}