001package com.nimbusds.oauth2.sdk; 002 003 004import java.util.Date; 005import java.util.List; 006 007import com.nimbusds.jwt.util.DateUtils; 008import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 009import com.nimbusds.oauth2.sdk.http.HTTPResponse; 010import com.nimbusds.oauth2.sdk.id.*; 011import com.nimbusds.oauth2.sdk.token.AccessTokenType; 012import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 013import net.jcip.annotations.Immutable; 014import net.minidev.json.JSONObject; 015 016 017/** 018 * Token introspection success response. 019 * 020 * <p>Related specifications: 021 * 022 * <ul> 023 * <li>OAuth 2.0 Token Introspection (RFC 7662). 024 * </ul> 025 */ 026@Immutable 027public class TokenIntrospectionSuccessResponse extends TokenIntrospectionResponse implements SuccessResponse { 028 029 030 /** 031 * Builder for constructing token introspection success responses. 032 */ 033 public static class Builder { 034 035 036 /** 037 * Determines whether the token is active. 038 */ 039 private final boolean active; 040 041 042 /** 043 * The optional token scope. 044 */ 045 private Scope scope; 046 047 048 /** 049 * The optional client ID for the token. 050 */ 051 private ClientID clientID; 052 053 054 /** 055 * The optional username for the token. 056 */ 057 private String username; 058 059 060 /** 061 * The optional token type. 062 */ 063 private AccessTokenType tokenType; 064 065 066 /** 067 * The optional token expiration date. 068 */ 069 private Date exp; 070 071 072 /** 073 * The optional token issue date. 074 */ 075 private Date iat; 076 077 078 /** 079 * The optional token not-before date. 080 */ 081 private Date nbf; 082 083 084 /** 085 * The optional token subject. 086 */ 087 private Subject sub; 088 089 090 /** 091 * The optional token audience. 092 */ 093 private List<Audience> audList; 094 095 096 /** 097 * The optional token issuer. 098 */ 099 private Issuer iss; 100 101 102 /** 103 * The optional token identifier. 104 */ 105 private JWTID jti; 106 107 108 /** 109 * Optional custom parameters. 110 */ 111 private final JSONObject customParams = new JSONObject(); 112 113 114 /** 115 * Creates a new token introspection success response builder. 116 * 117 * @param active {@code true} if the token is active, else 118 * {@code false}. 119 */ 120 public Builder(final boolean active) { 121 122 this.active = active; 123 } 124 125 126 /** 127 * Sets the token scope. 128 * 129 * @param scope The token scope, {@code null} if not specified. 130 * 131 * @return This builder. 132 */ 133 public Builder scope(final Scope scope) { 134 this.scope = scope; 135 return this; 136 } 137 138 139 /** 140 * Sets the identifier for the OAuth 2.0 client that requested 141 * the token. 142 * 143 * @param clientID The client identifier, {@code null} if not 144 * specified. 145 * 146 * @return This builder. 147 */ 148 public Builder clientID(final ClientID clientID) { 149 this.clientID = clientID; 150 return this; 151 } 152 153 154 /** 155 * Sets the username of the resource owner who authorised the 156 * token. 157 * 158 * @param username The username, {@code null} if not specified. 159 * 160 * @return This builder. 161 */ 162 public Builder username(final String username) { 163 this.username = username; 164 return this; 165 } 166 167 168 /** 169 * Sets the token type. 170 * 171 * @param tokenType The token type, {@code null} if not 172 * specified. 173 * 174 * @return This builder. 175 */ 176 public Builder tokenType(final AccessTokenType tokenType) { 177 this.tokenType = tokenType; 178 return this; 179 } 180 181 182 /** 183 * Sets the token expiration time. 184 * 185 * @param exp The token expiration time, {@code null} if not 186 * specified. 187 * 188 * @return This builder. 189 */ 190 public Builder expirationTime(final Date exp) { 191 this.exp = exp; 192 return this; 193 } 194 195 196 /** 197 * Sets the token issue time. 198 * 199 * @param iat The token issue time, {@code null} if not 200 * specified. 201 * 202 * @return This builder. 203 */ 204 public Builder issueTime(final Date iat) { 205 this.iat = iat; 206 return this; 207 } 208 209 210 /** 211 * Sets the token not-before time. 212 * 213 * @param nbf The token not-before time, {@code null} if not 214 * specified. 215 * 216 * @return This builder. 217 */ 218 public Builder notBeforeTime(final Date nbf) { 219 this.nbf = nbf; 220 return this; 221 } 222 223 224 /** 225 * Sets the token subject. 226 * 227 * @param sub The token subject, {@code null} if not specified. 228 * 229 * @return This builder. 230 */ 231 public Builder subject(final Subject sub) { 232 this.sub = sub; 233 return this; 234 } 235 236 237 /** 238 * Sets the token audience. 239 * 240 * @param audList The token audience, {@code null} if not 241 * specified. 242 * 243 * @return This builder. 244 */ 245 public Builder audience(final List<Audience> audList) { 246 this.audList = audList; 247 return this; 248 } 249 250 251 /** 252 * Sets the token issuer. 253 * 254 * @param iss The token issuer, {@code null} if not specified. 255 * 256 * @return This builder. 257 */ 258 public Builder issuer(final Issuer iss) { 259 this.iss = iss; 260 return this; 261 } 262 263 264 /** 265 * Sets the token identifier. 266 * 267 * @param jti The token identifier, {@code null} if not 268 * specified. 269 * 270 * @return This builder. 271 */ 272 public Builder jwtID(final JWTID jti) { 273 this.jti = jti; 274 return this; 275 } 276 277 278 /** 279 * Sets a custom parameter. 280 * 281 * @param name The parameter name. Must not be {@code null}. 282 * @param value The parameter value. Should map to a JSON type. 283 * If {@code null} not specified. 284 * 285 * @return This builder. 286 */ 287 public Builder parameter(final String name, final Object value) { 288 if (value != null) { 289 customParams.put(name, value); 290 } 291 return this; 292 } 293 294 295 /** 296 * Builds a new token introspection success response. 297 * 298 * @return The token introspection success response. 299 */ 300 public TokenIntrospectionSuccessResponse build() { 301 302 JSONObject o = new JSONObject(); 303 o.put("active", active); 304 if (scope != null) o.put("scope", scope.toString()); 305 if (clientID != null) o.put("client_id", clientID.getValue()); 306 if (username != null) o.put("username", username); 307 if (tokenType != null) o.put("token_type", tokenType.getValue()); 308 if (exp != null) o.put("exp", DateUtils.toSecondsSinceEpoch(exp)); 309 if (iss != null) o.put("iat", DateUtils.toSecondsSinceEpoch(iat)); 310 if (nbf != null) o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf)); 311 if (sub != null) o.put("sub", sub.getValue()); 312 if (audList != null) o.put("aud", Audience.toStringList(audList)); 313 if (iss != null) o.put("iss", iss.getValue()); 314 if (jti != null) o.put("jti", jti.getValue()); 315 o.putAll(customParams); 316 return new TokenIntrospectionSuccessResponse(o); 317 } 318 } 319 320 321 /** 322 * The parameters. 323 */ 324 private final JSONObject params; 325 326 327 /** 328 * Creates a new token introspection success response. 329 * 330 * @param params The response parameters. Must contain at least the 331 * required {@code active} parameter and not be 332 * {@code null}. 333 */ 334 public TokenIntrospectionSuccessResponse(final JSONObject params) { 335 336 if (! (params.get("active") instanceof Boolean)) { 337 throw new IllegalArgumentException("Missing / invalid boolean active parameter"); 338 } 339 340 this.params = params; 341 } 342 343 344 /** 345 * Returns the active status for the token. Corresponds to the 346 * {@code active} claim. 347 * 348 * @return {@code true} if the token is active, else {@code false}. 349 */ 350 public boolean isActive() { 351 352 try { 353 return JSONObjectUtils.getBoolean(params, "active"); 354 } catch (ParseException e) { 355 return false; // always false on error 356 } 357 } 358 359 360 /** 361 * Returns the scope of the token. Corresponds to the {@code scope} 362 * claim. 363 * 364 * @return The token scope, {@code null} if not specified. 365 */ 366 public Scope getScope() { 367 368 try { 369 return Scope.parse(JSONObjectUtils.getString(params, "scope")); 370 } catch (ParseException e) { 371 return null; 372 } 373 } 374 375 376 /** 377 * Returns the identifier of the OAuth 2.0 client that requested the 378 * token. Corresponds to the {@code client_id} claim. 379 * 380 * @return The client identifier, {@code null} if not specified. 381 */ 382 public ClientID getClientID() { 383 384 try { 385 return new ClientID(JSONObjectUtils.getString(params, "client_id")); 386 } catch (ParseException e) { 387 return null; 388 } 389 } 390 391 392 /** 393 * Returns the username of the resource owner who authorised the token. 394 * Corresponds to the {@code username} claim. 395 * 396 * @return The username, {@code null} if not specified. 397 */ 398 public String getUsername() { 399 400 try { 401 return JSONObjectUtils.getString(params, "username"); 402 } catch (ParseException e) { 403 return null; 404 } 405 } 406 407 408 /** 409 * Returns the access token type. Corresponds to the {@code token_type} 410 * claim. 411 * 412 * @return The token type, {@code null} if not specified. 413 */ 414 public AccessTokenType getTokenType() { 415 416 try { 417 return new AccessTokenType(JSONObjectUtils.getString(params, "token_type")); 418 } catch (ParseException e) { 419 return null; 420 } 421 } 422 423 424 /** 425 * Returns the token expiration time. Corresponds to the {@code exp} 426 * claim. 427 * 428 * @return The token expiration time, {@code null} if not specified. 429 */ 430 public Date getExpirationTime() { 431 432 try { 433 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "exp")); 434 } catch (ParseException e) { 435 return null; 436 } 437 } 438 439 440 /** 441 * Returns the token issue time. Corresponds to the {@code iat} claim. 442 * 443 * @return The token issue time, {@code null} if not specified. 444 */ 445 public Date getIssueTime() { 446 447 try { 448 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "iat")); 449 } catch (ParseException e) { 450 return null; 451 } 452 } 453 454 455 /** 456 * Returns the token not-before time. Corresponds to the {@code nbf} 457 * claim. 458 * 459 * @return The token not-before time, {@code null} if not specified. 460 */ 461 public Date getNotBeforeTime() { 462 463 try { 464 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "nbf")); 465 } catch (ParseException e) { 466 return null; 467 } 468 } 469 470 471 /** 472 * Returns the subject of the token, usually a machine-readable 473 * identifier of the resource owner who authorised the token. 474 * Corresponds to the {@code sub} claim. 475 * 476 * @return The token subject, {@code null} if not specified. 477 */ 478 public Subject getSubject() { 479 480 try { 481 return new Subject(JSONObjectUtils.getString(params, "sub")); 482 } catch (ParseException e) { 483 return null; 484 } 485 } 486 487 488 /** 489 * Returns the intended audience for the token. Corresponds to the 490 * {@code aud} claim. 491 * 492 * @return The token audience, {@code null} if not specified. 493 */ 494 public List<Audience> getAudience() { 495 // Try string array first, then string 496 try { 497 return Audience.create(JSONObjectUtils.getStringList(params, "aud")); 498 } catch (ParseException e) { 499 try { 500 return new Audience(JSONObjectUtils.getString(params, "aud")).toSingleAudienceList(); 501 } catch (ParseException e2) { 502 return null; 503 } 504 } 505 } 506 507 508 /** 509 * Returns the token issuer. Corresponds to the {@code iss} claim. 510 * 511 * @return The token issuer, {@code null} if not specified. 512 */ 513 public Issuer getIssuer() { 514 515 try { 516 return new Issuer(JSONObjectUtils.getString(params, "iss")); 517 } catch (ParseException e) { 518 return null; 519 } 520 } 521 522 523 /** 524 * Returns the token identifier. Corresponds to the {@code jti} 525 * claim. 526 * 527 * @return The token identifier, {@code null} if not specified. 528 */ 529 public JWTID getJWTID() { 530 531 try { 532 return new JWTID(JSONObjectUtils.getString(params, "jti")); 533 } catch (ParseException e) { 534 return null; 535 } 536 } 537 538 539 /** 540 * Returns a JSON object representation of this token introspection 541 * success response. 542 * 543 * <p>Example JSON object: 544 * 545 * <pre> 546 * { 547 * "active" : true, 548 * "client_id" : "l238j323ds-23ij4", 549 * "username" : "jdoe", 550 * "scope" : "read write dolphin", 551 * "sub" : "Z5O3upPC88QrAjx00dis", 552 * "aud" : "https://protected.example.net/resource", 553 * "iss" : "https://server.example.com/", 554 * "exp" : 1419356238, 555 * "iat" : 1419350238, 556 * "extension_field" : "twenty-seven" 557 * } 558 * </pre> 559 * 560 * @return The JSON object. 561 */ 562 public JSONObject toJSONObject() { 563 564 return new JSONObject(params); 565 } 566 567 568 @Override 569 public boolean indicatesSuccess() { 570 571 return true; 572 } 573 574 575 @Override 576 public HTTPResponse toHTTPResponse() { 577 578 HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK); 579 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 580 httpResponse.setContent(params.toJSONString()); 581 return httpResponse; 582 } 583 584 585 /** 586 * Parses a token introspection success response from the specified 587 * JSON object. 588 * 589 * @param jsonObject The JSON object to parse. Must not be {@code null}. 590 * 591 * @return The token introspection success response. 592 * 593 * @throws ParseException If the JSON object couldn't be parsed to a 594 * token introspection success response. 595 */ 596 public static TokenIntrospectionSuccessResponse parse(final JSONObject jsonObject) 597 throws ParseException { 598 599 try { 600 return new TokenIntrospectionSuccessResponse(jsonObject); 601 } catch (IllegalArgumentException e) { 602 throw new ParseException(e.getMessage(), e); 603 } 604 } 605 606 607 /** 608 * Parses an token introspection success response from the specified 609 * HTTP response. 610 * 611 * @param httpResponse The HTTP response. Must not be {@code null}. 612 * 613 * @return The token introspection success response. 614 * 615 * @throws ParseException If the HTTP response couldn't be parsed to a 616 * token introspection success response. 617 */ 618 public static TokenIntrospectionSuccessResponse parse(final HTTPResponse httpResponse) 619 throws ParseException { 620 621 httpResponse.ensureStatusCode(HTTPResponse.SC_OK); 622 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 623 return parse(jsonObject); 624 } 625}