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 }