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.openid.connect.sdk.claims;
019
020
021import java.net.URI;
022import java.net.URL;
023import java.util.*;
024import javax.mail.internet.InternetAddress;
025
026import com.nimbusds.jose.util.DateUtils;
027import com.nimbusds.jwt.JWTClaimsSet;
028import com.nimbusds.langtag.LangTag;
029import com.nimbusds.langtag.LangTagUtils;
030import com.nimbusds.oauth2.sdk.ParseException;
031import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
032import net.minidev.json.JSONObject;
033
034
035/**
036 * Claims set serialisable to a JSON object.
037 */
038public abstract class ClaimsSet {
039
040
041        /**
042         * The JSON object representation of the claims set.
043         */
044        private final JSONObject claims;
045
046
047        /**
048         * Creates a new empty claims set.
049         */
050        protected ClaimsSet() {
051
052                claims = new JSONObject();
053        }
054
055
056        /**
057         * Creates a new claims set from the specified JSON object.
058         *
059         * @param jsonObject The JSON object. Must not be {@code null}.
060         */
061        protected ClaimsSet(final JSONObject jsonObject) {
062
063                if (jsonObject == null)
064                        throw new IllegalArgumentException("The JSON object must not be null");
065
066                claims = jsonObject;
067        }
068
069
070        /**
071         * Puts all claims from the specified other claims set.
072         *
073         * @param other The other claims set. Must not be {@code null}.
074         */
075        public void putAll(final ClaimsSet other) {
076
077                putAll(other.claims);
078        }
079
080
081        /**
082         * Puts all claims from the specified map.
083         *
084         * @param claims The claims to put. Must not be {@code null}.
085         */
086        public void putAll(final Map<String,Object> claims) {
087
088                this.claims.putAll(claims);
089        }
090
091
092        /**
093         * Gets a claim.
094         *
095         * @param name The claim name. Must not be {@code null}.
096         *
097         * @return The claim value, {@code null} if not specified.
098         */
099        public Object getClaim(final String name) {
100
101                return claims.get(name);
102        }
103
104
105        /**
106         * Gets a claim that casts to the specified class.
107         *
108         * @param name  The claim name. Must not be {@code null}.
109         * @param clazz The Java class that the claim value should cast to.
110         *              Must not be {@code null}.
111         *
112         * @return The claim value, {@code null} if not specified or casting
113         *         failed.
114         */
115        public <T> T getClaim(final String name, final Class<T> clazz) {
116
117                try {
118                        return JSONObjectUtils.getGeneric(claims, name, clazz);
119                } catch (ParseException e) {
120                        return null;
121                }
122        }
123
124
125        /**
126         * Returns a map of all instances, including language-tagged, of a
127         * claim with the specified base name.
128         *
129         * <p>Example JSON serialised claims set:
130         *
131         * <pre>
132         * {
133         *   "month"    : "January",
134         *   "month#de" : "Januar"
135         *   "month#es" : "enero",
136         *   "month#it" : "gennaio"
137         * }
138         * </pre>
139         *
140         * <p>The "month" claim instances as java.util.Map:
141         *
142         * <pre>
143         * null => "January" (no language tag)
144         * "de" => "Januar"
145         * "es" => "enero"
146         * "it" => "gennaio"
147         * </pre>
148         *
149         * @param name  The claim name. Must not be {@code null}.
150         * @param clazz The Java class that the claim values should cast to.
151         *              Must not be {@code null}.
152         *
153         * @return The matching language-tagged claim values, empty map if
154         *         none. A {@code null} key indicates the value has no language
155         *         tag (corresponds to the base name).
156         */
157        public <T> Map<LangTag,T> getLangTaggedClaim(final String name, final Class<T> clazz) {
158
159                Map<LangTag,Object> matches = LangTagUtils.find(name, claims);
160                Map<LangTag,T> out = new HashMap<>();
161
162                for (Map.Entry<LangTag,Object> entry: matches.entrySet()) {
163
164                        LangTag langTag = entry.getKey();
165                        String compositeKey = name + (langTag != null ? "#" + langTag : "");
166
167                        try {
168                                out.put(langTag, JSONObjectUtils.getGeneric(claims, compositeKey, clazz));
169                        } catch (ParseException e) {
170                                // skip
171                        }
172                }
173
174                return out;
175        }
176
177
178        /**
179         * Sets a claim.
180         *
181         * @param name  The claim name, with an optional language tag. Must not
182         *              be {@code null}.
183         * @param value The claim value. Should serialise to a JSON entity. If
184         *              {@code null} any existing claim with the same name will
185         *              be removed.
186         */
187        public void setClaim(final String name, final Object value) {
188
189                if (value != null)
190                        claims.put(name, value);
191                else
192                        claims.remove(name);
193        }
194
195
196        /**
197         * Sets a claim with an optional language tag.
198         *
199         * @param name    The claim name. Must not be {@code null}.
200         * @param value   The claim value. Should serialise to a JSON entity.
201         *                If {@code null} any existing claim with the same name
202         *                and language tag (if any) will be removed.
203         * @param langTag The language tag of the claim value, {@code null} if
204         *                not tagged.
205         */
206        public void setClaim(final String name, final Object value, final LangTag langTag) {
207
208                String keyName = langTag != null ? name + "#" + langTag : name;
209                setClaim(keyName, value);
210        }
211
212
213        /**
214         * Gets a string-based claim.
215         *
216         * @param name The claim name. Must not be {@code null}.
217         *
218         * @return The claim value, {@code null} if not specified or casting
219         *         failed.
220         */
221        public String getStringClaim(final String name) {
222
223                try {
224                        return JSONObjectUtils.getString(claims, name);
225                } catch (ParseException e) {
226                        return null;
227                }
228        }
229
230
231        /**
232         * Gets a string-based claim with an optional language tag.
233         *
234         * @param name    The claim name. Must not be {@code null}.
235         * @param langTag The language tag of the claim value, {@code null} to
236         *                get the non-tagged value.
237         *
238         * @return The claim value, {@code null} if not specified or casting
239         *         failed.
240         */
241        public String getStringClaim(final String name, final LangTag langTag) {
242
243                return langTag == null ? getStringClaim(name) : getStringClaim(name + '#' + langTag);
244        }
245
246
247        /**
248         * Gets a boolean-based claim.
249         *
250         * @param name The claim name. Must not be {@code null}.
251         *
252         * @return The claim value, {@code null} if not specified or casting
253         *         failed.
254         */
255        public Boolean getBooleanClaim(final String name) {
256
257                try {
258                        return JSONObjectUtils.getBoolean(claims, name);
259                } catch (ParseException e) {
260                        return null;
261                }
262        }
263
264
265        /**
266         * Gets a number-based claim.
267         *
268         * @param name The claim name. Must not be {@code null}.
269         *
270         * @return The claim value, {@code null} if not specified or casting
271         *         failed.
272         */
273        public Number getNumberClaim(final String name) {
274
275                try {
276                        return JSONObjectUtils.getNumber(claims, name);
277                } catch (ParseException e) {
278                        return null;
279                }
280        }
281
282
283        /**
284         * Gets an URL string based claim.
285         *
286         * @param name The claim name. Must not be {@code null}.
287         *
288         * @return The claim value, {@code null} if not specified or parsing
289         *         failed.
290         */
291        public URL getURLClaim(final String name) {
292
293                try {
294                        return JSONObjectUtils.getURL(claims, name);
295                } catch (ParseException e) {
296                        return null;
297                }
298        }
299
300
301        /**
302         * Sets an URL string based claim.
303         *
304         * @param name  The claim name. Must not be {@code null}.
305         * @param value The claim value. If {@code null} any existing claim
306         *              with the same name will be removed.
307         */
308        public void setURLClaim(final String name, final URL value) {
309
310                if (value != null)
311                        setClaim(name, value.toString());
312                else
313                        claims.remove(name);
314        }
315
316
317        /**
318         * Gets an URI string based claim.
319         *
320         * @param name The claim name. Must not be {@code null}.
321         *
322         * @return The claim value, {@code null} if not specified or parsing
323         *         failed.
324         */
325        public URI getURIClaim(final String name) {
326
327                try {
328                        return JSONObjectUtils.getURI(claims, name);
329                } catch (ParseException e) {
330                        return null;
331                }
332        }
333
334
335        /**
336         * Sets an URI string based claim.
337         *
338         * @param name  The claim name. Must not be {@code null}.
339         * @param value The claim value. If {@code null} any existing claim
340         *              with the same name will be removed.
341         */
342        public void setURIClaim(final String name, final URI value) {
343
344                if (value != null)
345                        setClaim(name, value.toString());
346                else
347                        claims.remove(name);
348        }
349
350
351        /**
352         * Gets an email string based claim.
353         *
354         * @param name The claim name. Must not be {@code null}.
355         *
356         * @return The claim value, {@code null} if not specified or parsing
357         *         failed.
358         */
359        public InternetAddress getEmailClaim(final String name) {
360
361                try {
362                        return JSONObjectUtils.getEmail(claims, name);
363                } catch (ParseException e) {
364                        return null;
365                }
366        }
367
368
369        /**
370         * Sets an email string based claim.
371         *
372         * @param name  The claim name. Must not be {@code null}.
373         * @param value The claim value. If {@code null} any existing claim
374         *              with the same name will be removed.
375         */
376        public void setEmailClaim(final String name, final InternetAddress value) {
377
378                if (value != null)
379                        setClaim(name, value.getAddress());
380                else
381                        claims.remove(name);
382        }
383
384
385        /**
386         * Gets a date / time based claim, represented as the number of seconds
387         * from 1970-01-01T0:0:0Z as measured in UTC until the date / time.
388         *
389         * @param name The claim name. Must not be {@code null}.
390         *
391         * @return The claim value, {@code null} if not specified or parsing
392         *         failed.
393         */
394        public Date getDateClaim(final String name) {
395
396                try {
397                        return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getNumber(claims, name).longValue());
398                } catch (Exception e) {
399                        return null;
400                }
401        }
402
403
404        /**
405         * Sets a date / time based claim, represented as the number of seconds
406         * from 1970-01-01T0:0:0Z as measured in UTC until the date / time.
407         *
408         * @param name  The claim name. Must not be {@code null}.
409         * @param value The claim value. If {@code null} any existing claim
410         *              with the same name will be removed.
411         */
412        public void setDateClaim(final String name, final Date value) {
413
414                if (value != null)
415                        setClaim(name, DateUtils.toSecondsSinceEpoch(value));
416                else
417                        claims.remove(name);
418        }
419
420
421        /**
422         * Gets a string list based claim.
423         *
424         * @param name The claim name. Must not be {@code null}.
425         *
426         * @return The claim value, {@code null} if not specified or parsing
427         *         failed.
428         */
429        public List<String> getStringListClaim(final String name) {
430
431                try {
432                        return Arrays.asList(JSONObjectUtils.getStringArray(claims, name));
433                } catch (ParseException e) {
434                        return null;
435                }
436        }
437
438
439        /**
440         * Gets the JSON object representation of this claims set.
441         *
442         * <p>Example:
443         *
444         * <pre>
445         * {
446         *   "country"       : "USA",
447         *   "country#en"    : "USA",
448         *   "country#de_DE" : "Vereinigte Staaten",
449         *   "country#fr_FR" : "Etats Unis"
450         * }
451         * </pre>
452         *
453         * @return The JSON object representation.
454         */
455        public JSONObject toJSONObject() {
456
457                return claims;
458        }
459
460
461        /**
462         * Gets the JSON Web Token (JWT) claims set for this claim set.
463         *
464         * @return The JWT claims set.
465         *
466         * @throws ParseException If the conversion to a JWT claims set fails.
467         */
468        public JWTClaimsSet toJWTClaimsSet()
469                throws ParseException {
470
471                try {
472                        return JWTClaimsSet.parse(claims);
473
474                } catch (java.text.ParseException e) {
475
476                        throw new ParseException(e.getMessage(), e);
477                }
478        }
479}