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