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