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