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