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