001package com.nimbusds.openid.connect.provider.spi.grants; 002 003 004import java.util.List; 005import java.util.Optional; 006 007import net.jcip.annotations.Immutable; 008import net.minidev.json.JSONObject; 009import org.checkerframework.checker.nullness.qual.Nullable; 010 011import com.nimbusds.oauth2.sdk.ParseException; 012import com.nimbusds.oauth2.sdk.id.Audience; 013import com.nimbusds.oauth2.sdk.id.Subject; 014import com.nimbusds.oauth2.sdk.token.TokenEncoding; 015import com.nimbusds.oauth2.sdk.util.CollectionUtils; 016import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 017import com.nimbusds.openid.connect.sdk.SubjectType; 018 019 020/** 021 * Access token specification. 022 */ 023@Immutable 024public class AccessTokenSpec extends TokenSpec { 025 026 027 /** 028 * The default access token specification. 029 * 030 * <ul> 031 * <li>No explicit token lifetime is specified, to let the 032 * Connect2id server apply the default configured lifetime for 033 * access tokens. 034 * <li>No explicit token audience is specified. 035 * <li>No subject in impersonation and delegation cases is 036 * specified. 037 * <li>The token is self-contained (JWT-encoded) and the optional 038 * encryption preference is not set. 039 * <li>The access token subject type is public. 040 * </ul> 041 */ 042 public static final AccessTokenSpec DEFAULT = new AccessTokenSpec(); 043 044 045 /** 046 * The access token encoding. 047 */ 048 private final TokenEncoding encoding; 049 050 051 /** 052 * The optional encryption preference for self-contained access 053 * tokens. 054 */ 055 private final Optional<Boolean> encryptSelfContained; 056 057 058 /** 059 * The access token subject type. 060 */ 061 private final SubjectType subjectType; 062 063 064 /** 065 * Creates a new default access token specification. No explicit 066 * token lifetime is specified (to let the Connect2id server apply the 067 * default configured lifetime for access tokens). No explicit token 068 * audience is specified. No subject in impersonation and delegation 069 * cases is specified. The token is self-contained (JWT-encoded) and 070 * the optional encryption preference is not set. The access token 071 * subject type is public. 072 */ 073 public AccessTokenSpec() { 074 075 this(0L, null, TokenEncoding.SELF_CONTAINED, null, Optional.empty(), SubjectType.PUBLIC); 076 } 077 078 079 /** 080 * Creates a new access token specification. No explicit token audience 081 * is specified. No subject in impersonation and delegation cases is 082 * specified. The access token subject type is public. 083 * 084 * @param lifetime The access token lifetime, in seconds, zero if not 085 * specified (to let the Connect2id server apply the 086 * default configured lifetime for access tokens). 087 * @param encoding The access token encoding. Must not be {@code null}. 088 * @param encrypt If {@code true} flags the access token for 089 * encryption. Applies to self-contained (JWT) access 090 * tokens only. 091 */ 092 @Deprecated 093 public AccessTokenSpec(final long lifetime, 094 final TokenEncoding encoding, 095 final boolean encrypt) { 096 097 this(lifetime, null, encoding, null, encrypt); 098 } 099 100 101 /** 102 * Creates a new access token specification. No subject in 103 * impersonation and delegation cases is specified. The access token 104 * subject type is public. 105 * 106 * @param lifetime The access token lifetime, in seconds, zero if not 107 * specified (to let the Connect2id server apply the 108 * default configured lifetime for access tokens). 109 * @param audList Explicit list of audiences for the access token, 110 * {@code null} if not specified. 111 * @param encoding The access token encoding. Must not be {@code null}. 112 * @param encrypt If {@code true} flags the access token for 113 * encryption. Applies to self-contained (JWT) access 114 * tokens only. 115 */ 116 @Deprecated 117 public AccessTokenSpec(final long lifetime, 118 final @Nullable List<Audience> audList, 119 final TokenEncoding encoding, 120 final boolean encrypt) { 121 122 this(lifetime, audList, encoding, null, encrypt); 123 } 124 125 126 /** 127 * Creates a new access token specification. The access token subject 128 * type is public. 129 * 130 * @param lifetime The access token lifetime, in seconds, 131 * zero if not specified (to let the 132 * Connect2id server apply the default 133 * configured lifetime for access tokens). 134 * @param audList Explicit list of audiences for the access 135 * token, {@code null} if not specified. 136 * @param encoding The access token encoding. Must not be 137 * {@code null}. 138 * @param impersonatedSubject The subject in impersonation and 139 * delegation cases, {@code null} if not 140 * applicable. 141 * @param encrypt If {@code true} flags the access token 142 * for encryption. Applies to self-contained 143 * (JWT) access tokens only. 144 */ 145 @Deprecated 146 public AccessTokenSpec(final long lifetime, 147 final @Nullable List<Audience> audList, 148 final TokenEncoding encoding, 149 final @Nullable Subject impersonatedSubject, 150 final boolean encrypt) { 151 152 this(lifetime, audList, encoding, impersonatedSubject, Optional.of(encrypt), SubjectType.PUBLIC); 153 } 154 155 156 /** 157 * Creates a new access token specification. 158 * 159 * @param lifetime The access token lifetime, in seconds, 160 * zero if not specified (to let the 161 * Connect2id server apply the default 162 * configured lifetime for access tokens). 163 * @param audList Explicit list of audiences for the access 164 * token, {@code null} if not specified. 165 * @param encoding The access token encoding. Must not be 166 * {@code null}. 167 * @param impersonatedSubject The subject in impersonation and 168 * delegation cases, {@code null} if not 169 * applicable. 170 * @param encrypt If {@code true} flags the access token 171 * for encryption. Applies to self-contained 172 * (JWT) access tokens only. 173 * @param subjectType The access token subject type. 174 */ 175 @Deprecated 176 public AccessTokenSpec(final long lifetime, 177 final @Nullable List<Audience> audList, 178 final TokenEncoding encoding, 179 final @Nullable Subject impersonatedSubject, 180 final boolean encrypt, 181 final SubjectType subjectType) { 182 183 this(lifetime, audList, encoding, impersonatedSubject, Optional.of(encrypt), subjectType); 184 } 185 186 187 /** 188 * Creates a new access token specification. No subject in 189 * impersonation and delegation cases is specified. The access token 190 * subject type is public. 191 * 192 * @param lifetime The access token lifetime, in seconds, 193 * zero if not specified (to let the 194 * Connect2id server apply the default 195 * configured lifetime for access tokens). 196 * @param audList Explicit list of audiences for the 197 * access token, {@code null} if not 198 * specified. 199 * @param encoding The access token encoding. Must not be 200 * {@code null}. 201 * @param encryptSelfContained The optional encryption preference for 202 * self-contained (JWT) access tokens. 203 * Must not be {@code null}. 204 */ 205 public AccessTokenSpec(final long lifetime, 206 final @Nullable List<Audience> audList, 207 final TokenEncoding encoding, 208 final Optional<Boolean> encryptSelfContained) { 209 210 this(lifetime, audList, encoding, null, encryptSelfContained, SubjectType.PUBLIC); 211 } 212 213 214 /** 215 * Creates a new access token specification. 216 * 217 * @param lifetime The access token lifetime, in seconds, 218 * zero if not specified (to let the 219 * Connect2id server apply the default 220 * configured lifetime for access tokens). 221 * @param audList Explicit list of audiences for the 222 * access token, {@code null} if not 223 * specified. 224 * @param encoding The access token encoding. Must not be 225 * {@code null}. 226 * @param impersonatedSubject The subject in impersonation and 227 * delegation cases, {@code null} if not 228 * applicable. 229 * @param encryptSelfContained The optional encryption preference for 230 * self-contained (JWT) access tokens. 231 * Must not be {@code null}. 232 * @param subjectType The access token subject type. 233 */ 234 public AccessTokenSpec(final long lifetime, 235 final @Nullable List<Audience> audList, 236 final TokenEncoding encoding, 237 final @Nullable Subject impersonatedSubject, 238 final Optional<Boolean> encryptSelfContained, 239 final SubjectType subjectType) { 240 241 super(lifetime, audList, impersonatedSubject); 242 243 if (encoding == null) { 244 throw new IllegalArgumentException("The access token encoding must not be null"); 245 } 246 247 this.encoding = encoding; 248 249 if (encryptSelfContained == null) { 250 throw new IllegalArgumentException("The optional encrypt self-contained must not be null"); 251 } 252 // Only JWT tokens may be encrypted 253 this.encryptSelfContained = TokenEncoding.SELF_CONTAINED.equals(encoding) ? encryptSelfContained : Optional.empty(); 254 255 if (subjectType == null) { 256 throw new IllegalArgumentException("The access token subject type must not be null"); 257 } 258 if (SubjectType.PAIRWISE.equals(subjectType) && CollectionUtils.isEmpty(audList)) { 259 throw new IllegalArgumentException("The pairwise token subject type requires an explicit token audience"); 260 } 261 this.subjectType = subjectType; 262 } 263 264 265 /** 266 * Returns the access token encoding. 267 * 268 * @return The access token encoding. 269 */ 270 public TokenEncoding getEncoding() { 271 272 return encoding; 273 } 274 275 276 /** 277 * Returns the access token encryption flag. 278 * 279 * @return If {@code true} the access token is flagged for encryption. 280 * Applies to self-contained access tokens only. 281 * 282 * @deprecated Use {@link #getEncryptSelfContained} instead. 283 */ 284 @Deprecated 285 public boolean encrypt() { 286 287 return encryptSelfContained.orElse(false); 288 } 289 290 291 /** 292 * Returns the optional encryption preference for self-contained (JWT) 293 * access tokens. 294 * 295 * @return The encryption preference. 296 */ 297 public Optional<Boolean> getEncryptSelfContained() { 298 299 return encryptSelfContained; 300 } 301 302 303 /** 304 * Returns the access token subject type. 305 * 306 * @return The subject type. 307 */ 308 public SubjectType getSubjectType() { 309 310 return subjectType; 311 } 312 313 314 @Override 315 public JSONObject toJSONObject() { 316 317 JSONObject o = super.toJSONObject(); 318 319 if (getLifetime() <= 0) { 320 // Implies not specified - remove 321 o.remove("lifetime"); 322 } 323 324 o.put("encoding", encoding.toString()); 325 326 if (encoding.equals(TokenEncoding.SELF_CONTAINED) && encryptSelfContained.isPresent()) { 327 o.put("encrypt", encryptSelfContained.get()); 328 } 329 330 o.put("sub_type", getSubjectType().toString().toUpperCase()); 331 332 return o; 333 } 334 335 336 /** 337 * Parses an access token specification from the specified JSON object. 338 * 339 * @param jsonObject The JSON object. Must not be {@code null}. 340 * 341 * @return The access token specification. 342 * 343 * @throws ParseException If parsing failed. 344 */ 345 public static AccessTokenSpec parse(final JSONObject jsonObject) 346 throws ParseException { 347 348 TokenSpec tokenSpec = TokenSpec.parse(jsonObject); 349 350 // Adjust lifetime value for not specified (0) 351 long lifetime = tokenSpec.getLifetime() > 0 ? tokenSpec.getLifetime() : 0; 352 353 TokenEncoding encoding = TokenEncoding.SELF_CONTAINED; 354 355 Optional<Boolean> encryptSelfContained = Optional.empty(); 356 357 if (jsonObject.get("encoding") != null) { 358 359 String c = JSONObjectUtils.getString(jsonObject, "encoding"); 360 361 try { 362 encoding = TokenEncoding.valueOf(c.toUpperCase()); 363 364 } catch (IllegalArgumentException e) { 365 366 throw new ParseException("Invalid access token encoding"); 367 } 368 } 369 370 if (encoding.equals(TokenEncoding.SELF_CONTAINED)) { 371 if (jsonObject.get("encrypt") != null) { 372 encryptSelfContained = Optional.of(JSONObjectUtils.getBoolean(jsonObject, "encrypt")); 373 } 374 } 375 376 SubjectType subjectType = JSONObjectUtils.getEnum(jsonObject, "sub_type", SubjectType.class, SubjectType.PUBLIC); 377 378 try { 379 return new AccessTokenSpec( 380 lifetime, 381 tokenSpec.getAudience(), 382 encoding, 383 tokenSpec.getImpersonatedSubject(), 384 encryptSelfContained, 385 subjectType 386 ); 387 } catch (IllegalArgumentException e) { 388 throw new ParseException(e.getMessage(), e); 389 } 390 } 391}