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}