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