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 * Creates a new token introspection success response builder 076 * with the parameters of the specified response. 077 * 078 * @param response The response which parameters to use. Not 079 * {@code null}. 080 */ 081 public Builder(final TokenIntrospectionSuccessResponse response) { 082 083 params.putAll(response.params); 084 } 085 086 087 /** 088 * Sets the token scope. 089 * 090 * @param scope The token scope, {@code null} if not specified. 091 * 092 * @return This builder. 093 */ 094 public Builder scope(final Scope scope) { 095 if (scope != null) params.put("scope", scope.toString()); 096 else params.remove("scope"); 097 return this; 098 } 099 100 101 /** 102 * Sets the identifier for the OAuth 2.0 client that requested 103 * the token. 104 * 105 * @param clientID The client identifier, {@code null} if not 106 * specified. 107 * 108 * @return This builder. 109 */ 110 public Builder clientID(final ClientID clientID) { 111 if (clientID != null) params.put("client_id", clientID.getValue()); 112 else params.remove("client_id"); 113 return this; 114 } 115 116 117 /** 118 * Sets the username of the resource owner who authorised the 119 * token. 120 * 121 * @param username The username, {@code null} if not specified. 122 * 123 * @return This builder. 124 */ 125 public Builder username(final String username) { 126 if (username != null) params.put("username", username); 127 else params.remove("username"); 128 return this; 129 } 130 131 132 /** 133 * Sets the token type. 134 * 135 * @param tokenType The token type, {@code null} if not 136 * specified. 137 * 138 * @return This builder. 139 */ 140 public Builder tokenType(final AccessTokenType tokenType) { 141 if (tokenType != null) params.put("token_type", tokenType.getValue()); 142 else params.remove("token_type"); 143 return this; 144 } 145 146 147 /** 148 * Sets the token expiration time. 149 * 150 * @param exp The token expiration time, {@code null} if not 151 * specified. 152 * 153 * @return This builder. 154 */ 155 public Builder expirationTime(final Date exp) { 156 if (exp != null) params.put("exp", DateUtils.toSecondsSinceEpoch(exp)); 157 else params.remove("exp"); 158 return this; 159 } 160 161 162 /** 163 * Sets the token issue time. 164 * 165 * @param iat The token issue time, {@code null} if not 166 * specified. 167 * 168 * @return This builder. 169 */ 170 public Builder issueTime(final Date iat) { 171 if (iat != null) params.put("iat", DateUtils.toSecondsSinceEpoch(iat)); 172 else params.remove("iat"); 173 return this; 174 } 175 176 177 /** 178 * Sets the token not-before time. 179 * 180 * @param nbf The token not-before time, {@code null} if not 181 * specified. 182 * 183 * @return This builder. 184 */ 185 public Builder notBeforeTime(final Date nbf) { 186 if (nbf != null) params.put("nbf", DateUtils.toSecondsSinceEpoch(nbf)); 187 else params.remove("nbf"); 188 return this; 189 } 190 191 192 /** 193 * Sets the token subject. 194 * 195 * @param sub The token subject, {@code null} if not specified. 196 * 197 * @return This builder. 198 */ 199 public Builder subject(final Subject sub) { 200 if (sub != null) params.put("sub", sub.getValue()); 201 else params.remove("sub"); 202 return this; 203 } 204 205 206 /** 207 * Sets the token audience. 208 * 209 * @param audList The token audience, {@code null} if not 210 * specified. 211 * 212 * @return This builder. 213 */ 214 public Builder audience(final List<Audience> audList) { 215 if (audList != null) params.put("aud", Audience.toStringList(audList)); 216 else params.remove("aud"); 217 return this; 218 } 219 220 221 /** 222 * Sets the token issuer. 223 * 224 * @param iss The token issuer, {@code null} if not specified. 225 * 226 * @return This builder. 227 */ 228 public Builder issuer(final Issuer iss) { 229 if (iss != null) params.put("iss", iss.getValue()); 230 else params.remove("iss"); 231 return this; 232 } 233 234 235 /** 236 * Sets the token identifier. 237 * 238 * @param jti The token identifier, {@code null} if not 239 * specified. 240 * 241 * @return This builder. 242 */ 243 public Builder jwtID(final JWTID jti) { 244 if (jti != null) params.put("jti", jti.getValue()); 245 else params.remove("jti"); 246 return this; 247 } 248 249 250 /** 251 * Sets the client X.509 certificate SHA-256 thumbprint, for a 252 * mutual TLS sender constrained access token. Corresponds to 253 * the {@code cnf.x5t#S256} claim. 254 * 255 * 256 * @return The client X.509 certificate SHA-256 thumbprint, 257 * {@code null} if not specified. 258 */ 259 public Builder x509CertificateSHA256Thumbprint(final Base64URL x5t) { 260 261 if (x5t != null) { 262 JSONObject cnf; 263 if (params.containsKey("cnf")) { 264 cnf = (JSONObject)params.get("cnf"); 265 } else { 266 cnf = new JSONObject(); 267 params.put("cnf", cnf); 268 } 269 cnf.put("x5t#S256", x5t.toString()); 270 } else if (params.containsKey("cnf")) { 271 JSONObject cnf = (JSONObject) params.get("cnf"); 272 cnf.remove("x5t#S256"); 273 if (cnf.isEmpty()) { 274 params.remove("cnf"); 275 } 276 } 277 278 return this; 279 } 280 281 282 /** 283 * Sets a custom parameter. 284 * 285 * @param name The parameter name. Must not be {@code null}. 286 * @param value The parameter value. Should map to a JSON type. 287 * If {@code null} not specified. 288 * 289 * @return This builder. 290 */ 291 public Builder parameter(final String name, final Object value) { 292 if (value != null) params.put(name, value); 293 else params.remove(name); 294 return this; 295 } 296 297 298 /** 299 * Builds a new token introspection success response. 300 * 301 * @return The token introspection success response. 302 */ 303 public TokenIntrospectionSuccessResponse build() { 304 305 return new TokenIntrospectionSuccessResponse(params); 306 } 307 } 308 309 310 /** 311 * The parameters. 312 */ 313 private final JSONObject params; 314 315 316 /** 317 * Creates a new token introspection success response. 318 * 319 * @param params The response parameters. Must contain at least the 320 * required {@code active} parameter and not be 321 * {@code null}. 322 */ 323 public TokenIntrospectionSuccessResponse(final JSONObject params) { 324 325 if (! (params.get("active") instanceof Boolean)) { 326 throw new IllegalArgumentException("Missing / invalid boolean active parameter"); 327 } 328 329 this.params = params; 330 } 331 332 333 /** 334 * Returns the active status for the token. Corresponds to the 335 * {@code active} claim. 336 * 337 * @return {@code true} if the token is active, else {@code false}. 338 */ 339 public boolean isActive() { 340 341 try { 342 return JSONObjectUtils.getBoolean(params, "active"); 343 } catch (ParseException e) { 344 return false; // always false on error 345 } 346 } 347 348 349 /** 350 * Returns the scope of the token. Corresponds to the {@code scope} 351 * claim. 352 * 353 * @return The token scope, {@code null} if not specified. 354 */ 355 public Scope getScope() { 356 357 try { 358 return Scope.parse(JSONObjectUtils.getString(params, "scope")); 359 } catch (ParseException e) { 360 return null; 361 } 362 } 363 364 365 /** 366 * Returns the identifier of the OAuth 2.0 client that requested the 367 * token. Corresponds to the {@code client_id} claim. 368 * 369 * @return The client identifier, {@code null} if not specified. 370 */ 371 public ClientID getClientID() { 372 373 try { 374 return new ClientID(JSONObjectUtils.getString(params, "client_id")); 375 } catch (ParseException e) { 376 return null; 377 } 378 } 379 380 381 /** 382 * Returns the username of the resource owner who authorised the token. 383 * Corresponds to the {@code username} claim. 384 * 385 * @return The username, {@code null} if not specified. 386 */ 387 public String getUsername() { 388 389 try { 390 return JSONObjectUtils.getString(params, "username"); 391 } catch (ParseException e) { 392 return null; 393 } 394 } 395 396 397 /** 398 * Returns the access token type. Corresponds to the {@code token_type} 399 * claim. 400 * 401 * @return The token type, {@code null} if not specified. 402 */ 403 public AccessTokenType getTokenType() { 404 405 try { 406 return new AccessTokenType(JSONObjectUtils.getString(params, "token_type")); 407 } catch (ParseException e) { 408 return null; 409 } 410 } 411 412 413 /** 414 * Returns the token expiration time. Corresponds to the {@code exp} 415 * claim. 416 * 417 * @return The token expiration time, {@code null} if not specified. 418 */ 419 public Date getExpirationTime() { 420 421 try { 422 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "exp")); 423 } catch (ParseException e) { 424 return null; 425 } 426 } 427 428 429 /** 430 * Returns the token issue time. Corresponds to the {@code iat} claim. 431 * 432 * @return The token issue time, {@code null} if not specified. 433 */ 434 public Date getIssueTime() { 435 436 try { 437 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "iat")); 438 } catch (ParseException e) { 439 return null; 440 } 441 } 442 443 444 /** 445 * Returns the token not-before time. Corresponds to the {@code nbf} 446 * claim. 447 * 448 * @return The token not-before time, {@code null} if not specified. 449 */ 450 public Date getNotBeforeTime() { 451 452 try { 453 return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "nbf")); 454 } catch (ParseException e) { 455 return null; 456 } 457 } 458 459 460 /** 461 * Returns the subject of the token, usually a machine-readable 462 * identifier of the resource owner who authorised the token. 463 * Corresponds to the {@code sub} claim. 464 * 465 * @return The token subject, {@code null} if not specified. 466 */ 467 public Subject getSubject() { 468 469 try { 470 return new Subject(JSONObjectUtils.getString(params, "sub")); 471 } catch (ParseException e) { 472 return null; 473 } 474 } 475 476 477 /** 478 * Returns the intended audience for the token. Corresponds to the 479 * {@code aud} claim. 480 * 481 * @return The token audience, {@code null} if not specified. 482 */ 483 public List<Audience> getAudience() { 484 // Try string array first, then string 485 try { 486 return Audience.create(JSONObjectUtils.getStringList(params, "aud")); 487 } catch (ParseException e) { 488 try { 489 return new Audience(JSONObjectUtils.getString(params, "aud")).toSingleAudienceList(); 490 } catch (ParseException e2) { 491 return null; 492 } 493 } 494 } 495 496 497 /** 498 * Returns the token issuer. Corresponds to the {@code iss} claim. 499 * 500 * @return The token issuer, {@code null} if not specified. 501 */ 502 public Issuer getIssuer() { 503 504 try { 505 return new Issuer(JSONObjectUtils.getString(params, "iss")); 506 } catch (ParseException e) { 507 return null; 508 } 509 } 510 511 512 /** 513 * Returns the token identifier. Corresponds to the {@code jti} 514 * claim. 515 * 516 * @return The token identifier, {@code null} if not specified. 517 */ 518 public JWTID getJWTID() { 519 520 try { 521 return new JWTID(JSONObjectUtils.getString(params, "jti")); 522 } catch (ParseException e) { 523 return null; 524 } 525 } 526 527 528 /** 529 * Returns the client X.509 certificate SHA-256 thumbprint, for a 530 * mutual TLS sender constrained access token. Corresponds to the 531 * {@code cnf.x5t#S256} claim. 532 * 533 * 534 * @return The client X.509 certificate SHA-256 thumbprint, 535 * {@code null} if not specified. 536 */ 537 public Base64URL getX509CertificateSHA256Thumbprint() { 538 539 try { 540 JSONObject cnf = JSONObjectUtils.getJSONObject(params, "cnf"); 541 return new Base64URL(JSONObjectUtils.getString(cnf, "x5t#S256")); 542 } catch (ParseException e) { 543 return null; 544 } 545 } 546 547 548 /** 549 * Returns the string parameter with the specified name. 550 * 551 * @param name The parameter name. Must not be {@code null}. 552 * 553 * @return The parameter value, {@code null} if not specified or if 554 * parsing failed. 555 */ 556 public String getStringParameter(final String name) { 557 558 try { 559 return JSONObjectUtils.getString(params, name); 560 } catch (ParseException e) { 561 return null; 562 } 563 } 564 565 566 /** 567 * Returns the boolean parameter with the specified name. 568 * 569 * @param name The parameter name. Must not be {@code null}. 570 * 571 * @return The parameter value. 572 * 573 * @throws ParseException If the parameter isn't specified or parsing 574 * failed. 575 */ 576 public boolean getBooleanParameter(final String name) 577 throws ParseException { 578 579 return JSONObjectUtils.getBoolean(params, name); 580 } 581 582 583 /** 584 * Returns the number parameter with the specified name. 585 * 586 * @param name The parameter name. Must not be {@code null}. 587 * 588 * @return The parameter value, {@code null} if not specified or 589 * parsing failed. 590 */ 591 public Number getNumberParameter(final String name) { 592 593 try { 594 return JSONObjectUtils.getNumber(params, name); 595 } catch (ParseException e) { 596 return null; 597 } 598 } 599 600 601 /** 602 * Returns the string list parameter with the specified name. 603 * 604 * @param name The parameter name. Must not be {@code null}. 605 * 606 * @return The parameter value, {@code null} if not specified or if 607 * parsing failed. 608 */ 609 public List<String> getStringListParameter(final String name) { 610 611 try { 612 return JSONObjectUtils.getStringList(params, name); 613 } catch (ParseException e) { 614 return null; 615 } 616 } 617 618 619 /** 620 * Returns the JSON object parameter with the specified name. 621 * 622 * @param name The parameter name. Must not be {@code null}. 623 * 624 * @return The parameter value, {@code null} if not specified or if 625 * parsing failed. 626 */ 627 public JSONObject getJSONObjectParameter(final String name) { 628 629 try { 630 return JSONObjectUtils.getJSONObject(params, name); 631 } catch (ParseException e) { 632 return null; 633 } 634 } 635 636 637 /** 638 * Returns the underlying parameters. 639 * 640 * @return The parameters, as JSON object. 641 */ 642 public JSONObject getParameters() { 643 644 return params; 645 } 646 647 648 /** 649 * Returns a JSON object representation of this token introspection 650 * success response. 651 * 652 * <p>Example JSON object: 653 * 654 * <pre> 655 * { 656 * "active" : true, 657 * "client_id" : "l238j323ds-23ij4", 658 * "username" : "jdoe", 659 * "scope" : "read write dolphin", 660 * "sub" : "Z5O3upPC88QrAjx00dis", 661 * "aud" : "https://protected.example.net/resource", 662 * "iss" : "https://server.example.com/", 663 * "exp" : 1419356238, 664 * "iat" : 1419350238, 665 * "extension_field" : "twenty-seven" 666 * } 667 * </pre> 668 * 669 * @return The JSON object. 670 */ 671 public JSONObject toJSONObject() { 672 673 return new JSONObject(params); 674 } 675 676 677 @Override 678 public boolean indicatesSuccess() { 679 680 return true; 681 } 682 683 684 @Override 685 public HTTPResponse toHTTPResponse() { 686 687 HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK); 688 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 689 httpResponse.setContent(params.toJSONString()); 690 return httpResponse; 691 } 692 693 694 /** 695 * Parses a token introspection success response from the specified 696 * JSON object. 697 * 698 * @param jsonObject The JSON object to parse. Must not be {@code null}. 699 * 700 * @return The token introspection success response. 701 * 702 * @throws ParseException If the JSON object couldn't be parsed to a 703 * token introspection success response. 704 */ 705 public static TokenIntrospectionSuccessResponse parse(final JSONObject jsonObject) 706 throws ParseException { 707 708 try { 709 return new TokenIntrospectionSuccessResponse(jsonObject); 710 } catch (IllegalArgumentException e) { 711 throw new ParseException(e.getMessage(), e); 712 } 713 } 714 715 716 /** 717 * Parses an token introspection success response from the specified 718 * HTTP response. 719 * 720 * @param httpResponse The HTTP response. Must not be {@code null}. 721 * 722 * @return The token introspection success response. 723 * 724 * @throws ParseException If the HTTP response couldn't be parsed to a 725 * token introspection success response. 726 */ 727 public static TokenIntrospectionSuccessResponse parse(final HTTPResponse httpResponse) 728 throws ParseException { 729 730 httpResponse.ensureStatusCode(HTTPResponse.SC_OK); 731 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 732 return parse(jsonObject); 733 } 734}