001package com.nimbusds.openid.connect.sdk.claims; 002 003 004import java.net.MalformedURLException; 005import java.net.URL; 006import java.text.ParseException; 007import java.util.*; 008 009import javax.mail.internet.AddressException; 010import javax.mail.internet.InternetAddress; 011 012import net.minidev.json.JSONArray; 013import net.minidev.json.JSONObject; 014 015import com.nimbusds.langtag.LangTag; 016import com.nimbusds.langtag.LangTagUtils; 017 018import com.nimbusds.jwt.JWTClaimsSet; 019 020import com.nimbusds.oauth2.sdk.util.DateUtils; 021 022 023/** 024 * Claims set serialisable to a JSON object. 025 * 026 * @author Vladimir Dzhuvinov 027 */ 028public abstract class ClaimsSet { 029 030 031 /** 032 * The JSON object representing the claims set. 033 */ 034 private final JSONObject claims; 035 036 037 /** 038 * Creates a new empty claims set. 039 */ 040 protected ClaimsSet() { 041 042 claims = new JSONObject(); 043 } 044 045 046 /** 047 * Creates a new claims set from the specified JSON object. 048 * 049 * @param jsonObject The JSON object. Must not be {@code null}. 050 */ 051 protected ClaimsSet(final JSONObject jsonObject) { 052 053 if (jsonObject == null) 054 throw new IllegalArgumentException("The JSON object must not be null"); 055 056 claims = jsonObject; 057 } 058 059 060 /** 061 * Puts all claims from the specified other claims set. 062 * 063 * @param other The other claims set. Must not be {@code null}. 064 */ 065 public void putAll(final ClaimsSet other) { 066 067 claims.putAll(other.claims); 068 } 069 070 071 /** 072 * Gets a claim. 073 * 074 * @param name The claim name. Must not be {@code null}. 075 * 076 * @return The claim value, {@code null} if not specified. 077 */ 078 public Object getClaim(final String name) { 079 080 return claims.get(name); 081 } 082 083 084 /** 085 * Gets a claim that casts to the specified class. 086 * 087 * @param name The claim name. Must not be {@code null}. 088 * @param clazz The Java class that the claim value should cast to. 089 * Must not be {@code null}. 090 * 091 * @return The claim value, {@code null} if not specified or casting 092 * failed. 093 */ 094 @SuppressWarnings("unchecked") 095 public <T> T getClaim(final String name, final Class<T> clazz) { 096 097 try { 098 return (T)claims.get(name); 099 100 } catch (ClassCastException e) { 101 102 return null; 103 } 104 } 105 106 107 /** 108 * Returns a map of all instances, including language-tagged, of a 109 * claim with the specified base name. 110 * 111 * <p>Example JSON serialised claims set: 112 * 113 * <pre> 114 * { 115 * "month" : "January", 116 * "month#de" : "Januar" 117 * "month#es" : "enero", 118 * "month#it" : "gennaio" 119 * } 120 * </pre> 121 * 122 * <p>The "month" claim instances as java.util.Map: 123 * 124 * <pre> 125 * null => "January" (no language tag) 126 * "de" => "Januar" 127 * "es" => "enero" 128 * "it" => "gennaio" 129 * </pre> 130 * 131 * @param name The claim name. Must not be {@code null}. 132 * @param clazz The Java class that the claim values should cast to. 133 * Must not be {@code null}. 134 * 135 * @return The matching language-tagged claim values, empty map if 136 * none. A {@code null} key indicates the value has no language 137 * tag (corresponds to the base name). 138 */ 139 @SuppressWarnings("unchecked") 140 public <T> Map<LangTag,T> getLangTaggedClaim(final String name, final Class<T> clazz) { 141 142 Map<LangTag,Object> matches = LangTagUtils.find(name, claims); 143 144 Map<LangTag,T> out = new HashMap<LangTag,T>(); 145 146 for (Map.Entry<LangTag,Object> entry: matches.entrySet()) { 147 148 try { 149 out.put(entry.getKey(), (T)entry.getValue()); 150 151 } catch (ClassCastException e) { 152 // skip 153 } 154 } 155 156 return out; 157 } 158 159 160 /** 161 * Sets a claim. 162 * 163 * @param name The claim name, with an optional language tag. Must not 164 * be {@code null}. 165 * @param value The claim value. Should serialise to a JSON entity. If 166 * {@code null} any existing claim with the same name will 167 * be removed. 168 */ 169 public void setClaim(final String name, final Object value) { 170 171 if (value != null) 172 claims.put(name, value); 173 else 174 claims.remove(name); 175 } 176 177 178 /** 179 * Sets a claim with an optional language tag. 180 * 181 * @param name The claim name. Must not be {@code null}. 182 * @param value The claim value. Should serialise to a JSON entity. 183 * If {@code null} any existing claim with the same name 184 * and language tag (if any) will be removed. 185 * @param langTag The language tag of the claim value, {@code null} if 186 * not tagged. 187 */ 188 public <T> void setClaim(final String name, final Object value, final LangTag langTag) { 189 190 String keyName = langTag != null ? name + "#" + langTag : name; 191 192 setClaim(keyName, value); 193 } 194 195 196 /** 197 * Gets a string-based claim. 198 * 199 * @param name The claim name. Must not be {@code null}. 200 * 201 * @return The claim value, {@code null} if not specified or casting 202 * failed. 203 */ 204 public String getStringClaim(final String name) { 205 206 return getClaim(name, String.class); 207 } 208 209 210 /** 211 * Gets a string-based claim with an optional language tag. 212 * 213 * @param name The claim name. Must not be {@code null}. 214 * @param langTag The language tag of the claim value, {@code null} to 215 * get the non-tagged value. 216 * 217 * @return The claim value, {@code null} if not specified or casting 218 * failed. 219 */ 220 public String getStringClaim(final String name, final LangTag langTag) { 221 222 if (langTag == null) 223 return getStringClaim(name); 224 else 225 return getStringClaim(name + '#' + langTag); 226 } 227 228 229 /** 230 * Gets a boolean-based claim. 231 * 232 * @param name The claim name. Must not be {@code null}. 233 * 234 * @return The claim value, {@code null} if not specified or casting 235 * failed. 236 */ 237 public Boolean getBooleanClaim(final String name) { 238 239 return getClaim(name, Boolean.class); 240 } 241 242 243 /** 244 * Gets a number-based claim. 245 * 246 * @param name The claim name. Must not be {@code null}. 247 * 248 * @return The claim value, {@code null} if not specified or casting 249 * failed. 250 */ 251 public Number getNumberClaim(final String name) { 252 253 return getClaim(name, Number.class); 254 } 255 256 257 /** 258 * Gets an URL string based claim. 259 * 260 * @param name The claim name. Must not be {@code null}. 261 * 262 * @return The claim value, {@code null} if not specified or parsing 263 * failed. 264 */ 265 public URL getURLClaim(final String name) { 266 267 String value = getStringClaim(name); 268 269 if (value == null) 270 return null; 271 272 try { 273 return new URL(value); 274 275 } catch (MalformedURLException e) { 276 277 return null; 278 } 279 } 280 281 282 /** 283 * Sets an URL string based claim. 284 * 285 * @param name The claim name. Must not be {@code null}. 286 * @param value The claim value. If {@code null} any existing claim 287 * with the same name will be removed. 288 */ 289 public void setURLClaim(final String name, final URL value) { 290 291 if (value != null) 292 setClaim(name, value.toString()); 293 else 294 claims.remove(name); 295 } 296 297 298 /** 299 * Gets an email string based claim. 300 * 301 * @param name The claim name. Must not be {@code null}. 302 * 303 * @return The claim value, {@code null} if not specified or parsing 304 * failed. 305 */ 306 public InternetAddress getEmailClaim(final String name) { 307 308 String value = getStringClaim(name); 309 310 if (value == null) 311 return null; 312 313 try { 314 return new InternetAddress(value); 315 316 } catch (AddressException e) { 317 318 return null; 319 } 320 } 321 322 323 /** 324 * Sets an email string based claim. 325 * 326 * @param name The claim name. Must not be {@code null}. 327 * @param value The claim value. If {@code null} any existing claim 328 * with the same name will be removed. 329 */ 330 public void setEmailClaim(final String name, final InternetAddress value) { 331 332 if (value != null) 333 setClaim(name, value.getAddress()); 334 else 335 claims.remove(name); 336 } 337 338 339 /** 340 * Gets a date / time based claim, represented as the number of seconds 341 * from 1970-01-01T0:0:0Z as measured in UTC until the date / time. 342 * 343 * @param name The claim name. Must not be {@code null}. 344 * 345 * @return The claim value, {@code null} if not specified or parsing 346 * failed. 347 */ 348 public Date getDateClaim(final String name) { 349 350 Number value = getNumberClaim(name); 351 352 if (value == null) 353 return null; 354 355 try { 356 return DateUtils.fromSecondsSinceEpoch(value.longValue()); 357 358 } catch (Exception e) { 359 360 return null; 361 } 362 } 363 364 365 /** 366 * Sets a date / time based claim, represented as the number of seconds 367 * from 1970-01-01T0:0:0Z as measured in UTC until the date / time. 368 * 369 * @param name The claim name. Must not be {@code null}. 370 * @param value The claim value. If {@code null} any existing claim 371 * with the same name will be removed. 372 */ 373 public void setDateClaim(final String name, final Date value) { 374 375 if (value != null) 376 setClaim(name, DateUtils.toSecondsSinceEpoch(value)); 377 else 378 claims.remove(name); 379 } 380 381 382 /** 383 * Gets a string list based claim. 384 * 385 * @param name The claim name. Must not be {@code null}. 386 * 387 * @return The claim value, {@code null} if not specified or parsing 388 * failed. 389 */ 390 public List<String> getStringListClaim(final String name) { 391 392 @SuppressWarnings("unchecked") List<Object> rawList = getClaim(name, List.class); 393 394 if (rawList == null) 395 rawList = getClaim(name, JSONArray.class); 396 397 if (rawList == null) 398 return null; 399 400 List<String> outputList = new ArrayList<String>(rawList.size()); 401 402 for (Object item: rawList) { 403 404 if (item != null) 405 outputList.add(item.toString()); 406 } 407 408 return outputList; 409 } 410 411 412 /** 413 * Gets the JSON object representation of this claims set. 414 * 415 * <p>Example: 416 * 417 * <pre> 418 * { 419 * "country" : "USA", 420 * "country#en" : "USA", 421 * "country#de_DE" : "Vereinigte Staaten", 422 * "country#fr_FR" : "Etats Unis" 423 * } 424 * </pre> 425 * 426 * @return The JSON object representation. 427 */ 428 public JSONObject toJSONObject() { 429 430 return claims; 431 } 432 433 434 /** 435 * Gets the JSON Web Token (JWT) claims set for this claim set. 436 * 437 * @return The JWT claims set. 438 * 439 * @throws ParseException If the conversion to a JWT claims set fails. 440 */ 441 public JWTClaimsSet toJWTClaimsSet() 442 throws ParseException { 443 444 return JWTClaimsSet.parse(claims); 445 } 446}