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