View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2013-2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.jose.jwt;
18  
19  import static java.util.Collections.*;
20  import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.*;
21  
22  import java.net.URI;
23  import java.util.ArrayList;
24  import java.util.Date;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.forgerock.json.JsonValue;
29  import org.forgerock.json.jose.utils.IntDate;
30  import org.forgerock.json.jose.utils.StringOrURI;
31  
32  /**
33   * An implementation that holds a JWT's Claims Set.
34   * <p>
35   * Provides methods to set claims for all the reserved claim names as well as custom claims.
36   *
37   * @since 2.0.0
38   */
39  public class JwtClaimsSet extends JWObject implements Payload {
40  
41      /**
42       * Constructs a new, empty JwtClaimsSet.
43       */
44      public JwtClaimsSet() {
45      }
46  
47      /**
48       * Constructs a new JwtClaimsSet, with its claims set to the contents of the given Map.
49       *
50       * @param claims A Map containing the claims to be set in the Claims Set.
51       */
52      public JwtClaimsSet(Map<String, Object> claims) {
53          setClaims(claims);
54      }
55  
56      /**
57       * Gets the type of the contents of the Claims Set.
58       * <p>
59       * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ",
60       * with the same rules applying.
61       *
62       * @param type The Claims Set content type.
63       */
64      public void setType(String type) {
65          put(TYP.value(), type);
66      }
67  
68      /**
69       * Gets the type of the contents of the Claims Set.
70       * <p>
71       * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ",
72       * with the same rules applying.
73       *
74       * @return The Claims Set content type.
75       */
76      public String getType() {
77          return get(TYP.value()).asString();
78      }
79  
80      /**
81       * Sets the unique ID of the JWT.
82       *
83       * @param jwtId The JWT's ID.
84       */
85      public void setJwtId(String jwtId) {
86          put(JTI.value(), jwtId);
87      }
88  
89      /**
90       * Gets the unique ID of the JWT.
91       *
92       * @return The JWT's ID or {@code null} if claim not present.
93       */
94      public String getJwtId() {
95          return get(JTI.value()).asString();
96      }
97  
98      /**
99       * Sets the issuer this JWT was issued by.
100      * <p>
101      * The given issuer can be any arbitrary string without any ":" characters, if the string does contain a ":"
102      * character then it must be a valid URI.
103      *
104      * @param issuer The JWT's issuer.
105      */
106     public void setIssuer(String issuer) {
107         StringOrURI.validateStringOrURI(issuer);
108         put(ISS.value(), issuer);
109     }
110 
111     /**
112      * Sets the issuer this JWT was issued by.
113      *
114      * @param issuer The JWT's issuer.
115      */
116     public void setIssuer(URI issuer) {
117         put(ISS.value(), issuer.toString());
118     }
119 
120     /**
121      *
122      * Gets the issuer this JWT was issued by.
123      *
124      * @return The JWT's issuer or {@code null} if claim not present.
125      */
126     public String getIssuer() {
127         return get(ISS.value()).asString();
128     }
129 
130     /**
131      * Sets the subject this JWT is issued to.
132      * <p>
133      * The given subject can be any arbitrary string without any ":" characters, if the string does contain a ":"
134      * character then it must be a valid URI.
135      *
136      * @param subject The JWT's principal.
137      * @see #setSubject(java.net.URI)
138      */
139     public void setSubject(String subject) {
140         StringOrURI.validateStringOrURI(subject);
141         put(SUB.value(), subject);
142     }
143 
144     /**
145      * Sets the subject this JWT is issued to.
146      *
147      * @param subject The JWT's principal.
148      * @see #setSubject(String)
149      */
150     public void setSubject(URI subject) {
151         put(SUB.value(), subject.toString());
152     }
153 
154     /**
155      * Gets the subject this JWT is issued to.
156      *
157      * @return The JWT's principal or {@code null} if claim not present.
158      */
159     public String getSubject() {
160         return get(SUB.value()).asString();
161     }
162 
163     /**
164      * Adds an entry to the JWT's intended audience list, in the Claims Set.
165      * <p>
166      * The given audience can be any arbitrary string without any ":" characters, if the string does contain a ":"
167      * character then it must be a valid URI.
168      *
169      * @param audience The JWT's intended audience.
170      * @see #addAudience(java.net.URI)
171      */
172     public void addAudience(String audience) {
173         StringOrURI.validateStringOrURI(audience);
174         addAudienceWithTypeCheck(audience);
175     }
176 
177     /**
178      * Adds an entry to the JWT's intended audience list, in the Claims Set.
179      *
180      * @param audience The JWT's intended audience.
181      * @see #addAudience(String)
182      */
183     public void addAudience(URI audience) {
184         addAudienceWithTypeCheck(audience.toString());
185     }
186 
187     private void addAudienceWithTypeCheck(String audience) {
188         JsonValue audienceClaim = get(AUD.value());
189 
190         if (audienceClaim.isNull()) {
191             put(AUD.value(), audience);
192         } else if (audienceClaim.isList()) {
193             audienceClaim.asList().add(audience);
194         } else {
195             List<String> audienceList = new ArrayList<>();
196             audienceList.add(audienceClaim.asString());
197             audienceList.add(audience);
198             put(AUD.value(), audienceList);
199         }
200     }
201 
202     /**
203      * Gets the intended audience for the JWT from the Claims Set.
204      *
205      * @return The JWT's intended audience or {@code null} if claim not present.
206      */
207     public List<String> getAudience() {
208         JsonValue audience = get(AUD.value());
209         if (audience.isNull()) {
210             return null;
211         } else if (audience.isList()) {
212             return audience.asList(String.class);
213         } else {
214             return singletonList(audience.asString());
215         }
216     }
217 
218     /**
219      * Sets the time the JWT was issued at, in the Claims Set.
220      * <p>
221      * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
222      *
223      * @param issuedAtTime The JWT's issued at time.
224      */
225     public void setIssuedAtTime(Date issuedAtTime) {
226         put(IAT.value(), IntDate.toIntDate(issuedAtTime));
227     }
228 
229     /**
230      * Sets the time the JWT was issued at, in the Claims Set.
231      * <p>
232      * This method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
233      *
234      * @param issuedAtTime The JWT's issued at time as a long in seconds.
235      * @see #setIssuedAtTime(java.util.Date)
236      */
237     private void setIssuedAtTime(long issuedAtTime) {
238         put(IAT.value(), issuedAtTime);
239     }
240 
241     /**
242      * Gets the time the JWT was issued at, from the Claims Set.
243      *
244      * @return The JWT's issued at time or {@code null} if claim not present.
245      */
246     public Date getIssuedAtTime() {
247         return getDate(IAT.value());
248     }
249 
250     /**
251      * Sets the time the JWT is not allowed to be processed before, in the Claims Set.
252      * <p>
253      * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
254      *
255      * @param notBeforeTime The JWT's not before time.
256      */
257     public void setNotBeforeTime(Date notBeforeTime) {
258         put(NBF.value(), IntDate.toIntDate(notBeforeTime));
259     }
260 
261     /**
262      * Sets the time the JWT is not allowed to be processed before, in the Claims Set.
263      * <p>
264      * The method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
265      *
266      * @param notBeforeTime The JWT's not before time.
267      * @see #setNotBeforeTime(java.util.Date)
268      */
269     private void setNotBeforeTime(long notBeforeTime) {
270         put(NBF.value(), notBeforeTime);
271     }
272 
273     /**
274      * Gets the time the JWT is not allowed to be processed before, from the Claims Set.
275      *
276      * @return The JWT's not before time or {@code null} if claim not present.
277      */
278     public Date getNotBeforeTime() {
279         return getDate(NBF.value());
280     }
281 
282     /**
283      * Sets the expiration time of the JWT in the Claims Set.
284      * <p>
285      * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
286      *
287      * @param expirationTime The JWT's expiration time.
288      */
289     public void setExpirationTime(Date expirationTime) {
290         put(EXP.value(), IntDate.toIntDate(expirationTime));
291     }
292 
293     /**
294      * Sets the expiration time of the JWT in the Claims Set.
295      * <p>
296      * This method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
297      *
298      * @param expirationTime The JWT's expiration time as a long in seconds.
299      * @see #setExpirationTime(java.util.Date)
300      */
301     private void setExpirationTime(long expirationTime) {
302         put(EXP.value(), expirationTime);
303     }
304 
305     /**
306      * Gets the expiration time of the JWT from the Claims Set.
307      *
308      * @return The JWT's expiration time or {@code null} if claim not present.
309      */
310     public Date getExpirationTime() {
311         return getDate(EXP.value());
312     }
313 
314     /**
315      * Sets a claim with the specified name and value.
316      * <p>
317      * If the key matches one of the reserved claim names, then the relevant <tt>set</tt> method is called to set that
318      * claim with the specified name and value.
319      *
320      * @param key The claim name.
321      * @param value The claim value.
322      */
323     public void setClaim(String key, Object value) {
324 
325         JwtClaimsSetKey claimsSetKey = getClaimSetKey(key.toUpperCase());
326 
327         switch (claimsSetKey) {
328         case TYP: {
329             checkValueIsOfType(value, String.class);
330             setType((String) value);
331             break;
332         }
333         case JTI: {
334             checkValueIsOfType(value, String.class);
335             setJwtId((String) value);
336             break;
337         }
338         case ISS: {
339             if (isValueOfType(value, URI.class)) {
340                 setIssuer((URI) value);
341             } else {
342                 checkValueIsOfType(value, String.class);
343                 setIssuer((String) value);
344             }
345             break;
346         }
347         case SUB: {
348             if (isValueOfType(value, URI.class)) {
349                 setSubject((URI) value);
350             } else {
351                 checkValueIsOfType(value, String.class);
352                 setSubject((String) value);
353             }
354             break;
355         }
356         case AUD: {
357             if (isValueOfType(value, List.class)) {
358                 List<?> audienceList = (List<?>) value;
359                 for (Object audience : audienceList) {
360                     if (isValueOfType(audience, URI.class)) {
361                         addAudience((URI) audience);
362                     } else {
363                         checkValueIsOfType(audience, String.class);
364                         addAudience((String) audience);
365                     }
366                 }
367             } else {
368                 if (isValueOfType(value, URI.class)) {
369                     addAudience((URI) value);
370                 } else {
371                     checkValueIsOfType(value, String.class);
372                     addAudience((String) value);
373                 }
374             }
375             break;
376         }
377         case IAT: {
378             if (isValueOfType(value, Number.class)) {
379                 setIssuedAtTime(((Number) value).longValue());
380             } else {
381                 checkValueIsOfType(value, Date.class);
382                 setIssuedAtTime((Date) value);
383             }
384             break;
385         }
386         case NBF: {
387             if (isValueOfType(value, Number.class)) {
388                 setNotBeforeTime(((Number) value).longValue());
389             } else {
390                 checkValueIsOfType(value, Date.class);
391                 setNotBeforeTime((Date) value);
392             }
393             break;
394         }
395         case EXP: {
396             if (isValueOfType(value, Number.class)) {
397                 setExpirationTime(((Number) value).longValue());
398             } else {
399                 checkValueIsOfType(value, Date.class);
400                 setExpirationTime((Date) value);
401             }
402             break;
403         }
404         default: {
405             put(key, value);
406         }
407         }
408     }
409 
410     /**
411      * Sets claims using the values contained in the specified map.
412      *
413      * @param claims The Map to use to set the claims.
414      */
415     public void setClaims(Map<String, Object> claims) {
416         for (String key : claims.keySet()) {
417             setClaim(key, claims.get(key));
418         }
419     }
420 
421     /**
422      * Gets a claim value for the specified key.
423      * <p>
424      * If the key matches one of the reserved claim names, then the relevant <tt>get</tt> method is called to get that
425      * claim value.
426      *
427      * @param key The claim name.
428      * @return The value stored against the claim name.
429      */
430     public Object getClaim(String key) {
431 
432         JwtClaimsSetKey claimsSetKey = getClaimSetKey(key.toUpperCase());
433 
434         Object value;
435 
436         switch (claimsSetKey) {
437         case TYP: {
438             value = getType();
439             break;
440         }
441         case JTI: {
442             value = getJwtId();
443             break;
444         }
445         case ISS: {
446             value = getIssuer();
447             break;
448         }
449         case SUB: {
450             value = getSubject();
451             break;
452         }
453         case AUD: {
454             value = getAudience();
455             break;
456         }
457         case IAT: {
458             value = getIssuedAtTime();
459             break;
460         }
461         case NBF: {
462             value = getNotBeforeTime();
463             break;
464         }
465         case EXP: {
466             value = getExpirationTime();
467             break;
468         }
469         default: {
470             value = get(key).getObject();
471         }
472         }
473 
474         return value;
475     }
476 
477     /**
478      * Gets a claim value for the specified claim name and then casts it to the specified type.
479      *
480      * @param key The claim name.
481      * @param clazz The class of the required type.
482      * @param <T> The required type for the claim value.
483      * @return The value stored against the claim name.
484      * @see #getClaim(String)
485      */
486     public <T> T getClaim(String key, Class<T> clazz) {
487         return clazz.cast(getClaim(key));
488     }
489 
490     /**
491      * Builds the JWT's Claims Set into a <code>String</code> representation of a JSON object.
492      *
493      * @return A JSON string.
494      */
495     public String build() {
496         return toString();
497     }
498 
499     /**
500      * Returns the specified item value as a {@link Date}. If no such member value exists, then a JSON value containing
501      * {@code null} is returned.
502      *
503      * @param key the {@code Map} key identifying the item to return.
504      * @return a {@link Date} representing the value or {@code null}.
505      */
506     private Date getDate(final String key) {
507         final JsonValue value = get(key);
508         return value.isNull() ? null : IntDate.fromIntDate(value.asLong());
509     }
510 }