001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.json.jose.jwt; 018 019import static java.util.Collections.*; 020import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.*; 021 022import java.net.URI; 023import java.util.ArrayList; 024import java.util.Date; 025import java.util.List; 026import java.util.Map; 027 028import org.forgerock.json.JsonValue; 029import org.forgerock.json.jose.utils.IntDate; 030import org.forgerock.json.jose.utils.StringOrURI; 031 032/** 033 * An implementation that holds a JWT's Claims Set. 034 * <p> 035 * Provides methods to set claims for all the reserved claim names as well as custom claims. 036 * 037 * @since 2.0.0 038 */ 039public class JwtClaimsSet extends JWObject implements Payload { 040 041 /** 042 * Constructs a new, empty JwtClaimsSet. 043 */ 044 public JwtClaimsSet() { 045 } 046 047 /** 048 * Constructs a new JwtClaimsSet, with its claims set to the contents of the given Map. 049 * 050 * @param claims A Map containing the claims to be set in the Claims Set. 051 */ 052 public JwtClaimsSet(Map<String, Object> claims) { 053 setClaims(claims); 054 } 055 056 /** 057 * Gets the type of the contents of the Claims Set. 058 * <p> 059 * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ", 060 * with the same rules applying. 061 * 062 * @param type The Claims Set content type. 063 */ 064 public void setType(String type) { 065 put(TYP.value(), type); 066 } 067 068 /** 069 * Gets the type of the contents of the Claims Set. 070 * <p> 071 * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ", 072 * with the same rules applying. 073 * 074 * @return The Claims Set content type. 075 */ 076 public String getType() { 077 return get(TYP.value()).asString(); 078 } 079 080 /** 081 * Sets the unique ID of the JWT. 082 * 083 * @param jwtId The JWT's ID. 084 */ 085 public void setJwtId(String jwtId) { 086 put(JTI.value(), jwtId); 087 } 088 089 /** 090 * Gets the unique ID of the JWT. 091 * 092 * @return The JWT's ID or {@code null} if claim not present. 093 */ 094 public String getJwtId() { 095 return get(JTI.value()).asString(); 096 } 097 098 /** 099 * 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}