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