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