001/* 002 * Copyright 2023-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2023-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2023-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.controls; 037 038 039 040import java.util.Date; 041import java.util.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.ldap.sdk.BindResult; 047import com.unboundid.ldap.sdk.Control; 048import com.unboundid.ldap.sdk.DecodeableControl; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.ObjectTrio; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.json.JSONField; 061import com.unboundid.util.json.JSONObject; 062import com.unboundid.util.json.JSONString; 063import com.unboundid.util.json.JSONValue; 064 065import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 066 067 068 069/** 070 * This class provides a response control that may be used to convey the 071 * access token (and other associated information) generated in response to a 072 * {@link GenerateAccessTokenRequestControl} for a successful bind operation. 073 * <BR> 074 * <BLOCKQUOTE> 075 * <B>NOTE:</B> This class, and other classes within the 076 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 077 * supported for use against Ping Identity, UnboundID, and 078 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 079 * for proprietary functionality or for external specifications that are not 080 * considered stable or mature enough to be guaranteed to work in an 081 * interoperable way with other types of LDAP servers. 082 * </BLOCKQUOTE> 083 * <BR> 084 * This control has an OID of "1.3.6.1.4.1.30221.2.5.68", a criticality of 085 * false, and a value that is the string representation of a JSON object with 086 * the following fields: 087 * <UL> 088 * <LI> 089 * {@code token} -- The access token that was generated by the server. This 090 * field may be absent if an error occurred while attempting to generate the 091 * access token. 092 * </LI> 093 * <LI> 094 * {@code expiration-time} -- The time that the access token is expected to 095 * expire. If present, it will be formatted in the ISO 8601 format 096 * described in RFC 3339 (which may be decoded using the 097 * {@link StaticUtils#decodeRFC3339Time} method). If absent, then the 098 * access token may not expire. 099 * </LI> 100 * <LI> 101 * {@code error-message} -- An optional message that may explain the reason 102 * that an access token could not be generated for the request. 103 * </LI> 104 * </UL> 105 * 106 * @see GenerateAccessTokenRequestControl 107 */ 108@NotMutable() 109@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 110public final class GenerateAccessTokenResponseControl 111 extends Control 112 implements DecodeableControl 113{ 114 /** 115 * The OID (1.3.6.1.4.1.30221.2.5.68) for the generate access token response 116 * control. 117 */ 118 @NotNull public static final String GENERATE_ACCESS_TOKEN_RESPONSE_OID = 119 "1.3.6.1.4.1.30221.2.5.68"; 120 121 122 123 /** 124 * The name of the field used to hold the generated access token in the value 125 * of this control. 126 */ 127 @NotNull private static final String JSON_FIELD_ACCESS_TOKEN = "token"; 128 129 130 131 /** 132 * The name of the field used to hold the error message in the value of this 133 * control. 134 */ 135 @NotNull private static final String JSON_FIELD_ERROR_MESSAGE = 136 "error-message"; 137 138 139 140 /** 141 * The name of the field used to hold the access token expiration time in the 142 * value of this control. 143 */ 144 @NotNull private static final String JSON_FIELD_EXPIRATION_TIME = 145 "expiration-time"; 146 147 148 149 /** 150 * The serial version UID for this serializable class. 151 */ 152 private static final long serialVersionUID = -6071943602038789356L; 153 154 155 156 // The access token expiration time included in the control. 157 @Nullable private final Long expirationTime; 158 159 // The generated access token included in the control. 160 @Nullable private final String accesToken; 161 162 // The error message included in the control. 163 @Nullable private final String errorMessage; 164 165 166 167 /** 168 * Creates a new empty control instance that is intended to be used only for 169 * decoding controls via the {@code DecodeableControl} interface. 170 */ 171 GenerateAccessTokenResponseControl() 172 { 173 expirationTime = null; 174 accesToken = null; 175 errorMessage = null; 176 } 177 178 179 180 /** 181 * Creates a new generate access token response control with the provided 182 * information. 183 * 184 * @param accessToken The access token that was generated. It may be 185 * {@code null} if no access token was generated. 186 * @param expirationTime The time that the access token is expected to 187 * expire. It may be {@code null} if no access token 188 * was generated, or if the token does not have an 189 * expiration time. 190 * @param errorMessage An error message with the reason the access token 191 * was not generated. It may be {@code null} if the 192 * access token was generated successfully or if no 193 * error message is available. 194 */ 195 public GenerateAccessTokenResponseControl( 196 @Nullable final String accessToken, 197 @Nullable final Date expirationTime, 198 @Nullable final String errorMessage) 199 { 200 super(GENERATE_ACCESS_TOKEN_RESPONSE_OID, false, 201 new ASN1OctetString(encodeValueObject(accessToken, expirationTime, 202 errorMessage).toString())); 203 204 this.accesToken = accessToken; 205 this.errorMessage = errorMessage; 206 207 if (expirationTime == null) 208 { 209 this.expirationTime = null; 210 } 211 else 212 { 213 this.expirationTime = expirationTime.getTime(); 214 } 215 } 216 217 218 219 /** 220 * Creates a new generate access token response control with the provided 221 * information. 222 * 223 * @param oid The OID for the control. 224 * @param isCritical Indicates whether the control should be marked 225 * critical. 226 * @param value The encoded value for the control. This may be 227 * {@code null} if no value was provided. 228 * 229 * @throws LDAPException If the provided control cannot be decoded as a 230 * generate access token response control. 231 */ 232 public GenerateAccessTokenResponseControl( 233 @NotNull final String oid, 234 final boolean isCritical, 235 @Nullable final ASN1OctetString value) 236 throws LDAPException 237 { 238 super(oid, isCritical, value); 239 240 if (value == null) 241 { 242 throw new LDAPException(ResultCode.DECODING_ERROR, 243 ERR_GENERATE_ACCESS_TOKEN_RESPONSE_NO_VALUE.get()); 244 } 245 246 try 247 { 248 final JSONObject valueObject = new JSONObject(value.stringValue()); 249 final ObjectTrio<String,Date,String> valueElements = 250 decodeJSONObject(valueObject); 251 accesToken = valueElements.getFirst(); 252 errorMessage = valueElements.getThird(); 253 254 final Date expirationTimeDate = valueElements.getSecond(); 255 if (expirationTimeDate == null) 256 { 257 expirationTime = null; 258 } 259 else 260 { 261 expirationTime = expirationTimeDate.getTime(); 262 } 263 } 264 catch (final Exception e) 265 { 266 Debug.debugException(e); 267 throw new LDAPException(ResultCode.DECODING_ERROR, 268 ERR_GENERATE_ACCESS_TOKEN_RESPONSE_CANNOT_DECODE_VALUE.get( 269 StaticUtils.getExceptionMessage(e)), 270 e); 271 } 272 } 273 274 275 276 /** 277 * Decodes the provided JSON object as the value of a generate access token 278 * response control. 279 * 280 * @param valueObject The JSON object to use to decode the value of a 281 * generate access token response control. It must not 282 * be {@code null}. 283 * 284 * @return An {@code ObjectTrio} in which the first element is the access 285 * token, the second element is the expiration time, and the third 286 * element is the error message. Any or all of the elements may be 287 * {@code null}. 288 * 289 * @throws LDAPException If a problem occurs while attempting to decode the 290 * JSON object as a generate access token value. 291 */ 292 @NotNull() 293 private static ObjectTrio<String,Date,String> decodeJSONObject( 294 @NotNull final JSONObject valueObject) 295 throws LDAPException 296 { 297 final String accessToken = 298 valueObject.getFieldAsString(JSON_FIELD_ACCESS_TOKEN); 299 final String errorMessage = 300 valueObject.getFieldAsString(JSON_FIELD_ERROR_MESSAGE); 301 302 final Date expirationTime; 303 final String expirationTimeStr = 304 valueObject.getFieldAsString(JSON_FIELD_EXPIRATION_TIME); 305 if (expirationTimeStr == null) 306 { 307 expirationTime = null; 308 } 309 else 310 { 311 try 312 { 313 expirationTime = StaticUtils.decodeRFC3339Time(expirationTimeStr); 314 } 315 catch (final Exception e) 316 { 317 Debug.debugException(e); 318 throw new LDAPException(ResultCode.DECODING_ERROR, 319 ERR_GENERATE_ACCESS_TOKEN_RESPONSE_INVALID_TIMESTAMP.get( 320 valueObject.toSingleLineString(), 321 JSON_FIELD_EXPIRATION_TIME)); 322 } 323 } 324 325 return new ObjectTrio<>(accessToken, expirationTime, errorMessage); 326 } 327 328 329 330 /** 331 * {@inheritDoc} 332 */ 333 @Override() 334 @NotNull() 335 public GenerateAccessTokenResponseControl decodeControl( 336 @NotNull final String oid, 337 final boolean isCritical, 338 @Nullable final ASN1OctetString value) 339 throws LDAPException 340 { 341 return new GenerateAccessTokenResponseControl(oid, isCritical, value); 342 } 343 344 345 346 /** 347 * Extracts a generate access token response control from the provided result. 348 * 349 * @param result The result from which to retrieve the generate access token 350 * response control. 351 * 352 * @return The generate access token response control contained in the 353 * provided result, or {@code null} if the result did not contain a 354 * generate access token response control. 355 * 356 * @throws LDAPException If a problem is encountered while attempting to 357 * decode the generate access token response control 358 * contained in the provided result. 359 */ 360 @Nullable() 361 public static GenerateAccessTokenResponseControl get( 362 @NotNull final BindResult result) 363 throws LDAPException 364 { 365 final Control c = 366 result.getResponseControl(GENERATE_ACCESS_TOKEN_RESPONSE_OID); 367 if (c == null) 368 { 369 return null; 370 } 371 372 if (c instanceof GenerateAccessTokenResponseControl) 373 { 374 return (GenerateAccessTokenResponseControl) c; 375 } 376 else 377 { 378 return new GenerateAccessTokenResponseControl(c.getOID(), c.isCritical(), 379 c.getValue()); 380 } 381 } 382 383 384 385 /** 386 * Encodes the provided information into a JSON object suitable for use in 387 * the value of this control. 388 * 389 * @param accessToken The access token that was generated. It may be 390 * {@code null} if no access token was generated. 391 * @param expirationTime The time that the access token is expected to 392 * expire. It may be {@code null} if no access token 393 * was generated, or if the token does not have an 394 * expiration time. 395 * @param errorMessage An error message containing the reason the access 396 * token was not generated. It may be {@code null} if 397 * the access token was generated successfully or if 398 * no error message is available. 399 * 400 * @return A JSON object containing the encoded control value information. 401 */ 402 @NotNull() 403 private static JSONObject encodeValueObject( 404 @Nullable final String accessToken, 405 @Nullable final Date expirationTime, 406 @Nullable final String errorMessage) 407 { 408 return encodeValueObject(accessToken, 409 ((expirationTime == null) ? null : expirationTime.getTime()), 410 errorMessage); 411 } 412 413 414 415 /** 416 * Encodes the provided information into a JSON object suitable for use in 417 * the value of this control. 418 * 419 * @param accessToken The access token that was generated. It may be 420 * {@code null} if no access token was generated. 421 * @param expirationTime The time that the access token is expected to 422 * expire. It may be {@code null} if no access token 423 * was generated, or if the token does not have an 424 * expiration time. 425 * @param errorMessage An error message containing the reason the access 426 * token was not generated. It may be {@code null} if 427 * the access token was generated successfully or if 428 * no error message is available. 429 * 430 * @return A JSON object containing the encoded control value information. 431 */ 432 @NotNull() 433 private static JSONObject encodeValueObject( 434 @Nullable final String accessToken, 435 @Nullable final Long expirationTime, 436 @Nullable final String errorMessage) 437 { 438 final Map<String,JSONValue> fields = 439 new LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); 440 441 if (accessToken != null) 442 { 443 fields.put(JSON_FIELD_ACCESS_TOKEN, new JSONString(accessToken)); 444 } 445 446 if (expirationTime != null) 447 { 448 fields.put(JSON_FIELD_EXPIRATION_TIME, 449 new JSONString(StaticUtils.encodeRFC3339Time(expirationTime))); 450 } 451 452 if (errorMessage != null) 453 { 454 fields.put(JSON_FIELD_ERROR_MESSAGE, new JSONString(errorMessage)); 455 } 456 457 return new JSONObject(fields); 458 } 459 460 461 462 /** 463 * Retrieves the access token that was generated by the server. 464 * 465 * @return The access token that was generated by the server, or {@code null} 466 * if no access token was generated.. 467 */ 468 @Nullable() 469 public String getAccessToken() 470 { 471 return accesToken; 472 } 473 474 475 476 /** 477 * Retrieves the time that the generated access token is expected to expire. 478 * 479 * @return The time that the generated access token is expected to expire, or 480 * {@code null} if no access token was generated or if it does not 481 * have an expiration time. 482 */ 483 @Nullable() 484 public Date getExpirationTime() 485 { 486 if (expirationTime == null) 487 { 488 return null; 489 } 490 else 491 { 492 return new Date(expirationTime); 493 } 494 } 495 496 497 498 /** 499 * Retrieves an error message with the reason the access token was not 500 * generated. 501 * 502 * @return An error message with the reason the access token was not 503 * generated, or {@code null} if the access token was generated 504 * successfully or if no error message is available. 505 */ 506 @Nullable() 507 public String getErrorMessage() 508 { 509 return errorMessage; 510 } 511 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override() 518 @NotNull() 519 public String getControlName() 520 { 521 return INFO_CONTROL_NAME_GENERATE_ACCESS_TOKEN_RESPONSE.get(); 522 } 523 524 525 526 /** 527 * Retrieves a representation of this generate access token response control 528 * as a JSON object. The JSON object uses the following fields: 529 * <UL> 530 * <LI> 531 * {@code oid} -- A mandatory string field whose value is the object 532 * identifier for this control. For the generate access token response 533 * control, the OID is "1.3.6.1.4.1.30221.2.5.68". 534 * </LI> 535 * <LI> 536 * {@code control-name} -- An optional string field whose value is a 537 * human-readable name for this control. This field is only intended for 538 * descriptive purposes, and when decoding a control, the {@code oid} 539 * field should be used to identify the type of control. 540 * </LI> 541 * <LI> 542 * {@code criticality} -- A mandatory Boolean field used to indicate 543 * whether this control is considered critical. 544 * </LI> 545 * <LI> 546 * {@code value-base64} -- An optional string field whose value is a 547 * base64-encoded representation of the raw value for this generate access 548 * token response control. Exactly one of the {@code value-base64} and 549 * {@code value-json} fields must be present. 550 * </LI> 551 * <LI> 552 * {@code value-json} -- An optional JSON object field whose value is a 553 * user-friendly representation of the value for this generate access 554 * token response control. Exactly one of the {@code value-base64} and 555 * {@code value-json} fields must be present, and if the 556 * {@code value-json} field is used, then it will use the following 557 * fields: 558 * <UL> 559 * <LI> 560 * {@code token} -- An optional string field whose value is the access 561 * token that was generated. 562 * </LI> 563 * <LI> 564 * {@code expiration-time} -- An optional string field whose value is 565 * a timestamp indicating the time that the access token will expire, 566 * using the ISO 8601 format described in RFC 3339. 567 * </LI> 568 * <LI> 569 * {@code error-message} -- An optional string field whose value is an 570 * error message with the reason the access token was not generated. 571 * </LI> 572 * </UL> 573 * </LI> 574 * </UL> 575 * 576 * @return A JSON object that contains a representation of this control. 577 */ 578 @Override() 579 @NotNull() 580 public JSONObject toJSONControl() 581 { 582 return new JSONObject( 583 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 584 GENERATE_ACCESS_TOKEN_RESPONSE_OID), 585 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 586 INFO_CONTROL_NAME_GENERATE_ACCESS_TOKEN_RESPONSE.get()), 587 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 588 isCritical()), 589 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 590 encodeValueObject(accesToken, expirationTime, errorMessage))); 591 } 592 593 594 595 /** 596 * Attempts to decode the provided object as a JSON representation of a 597 * generate access token response control. 598 * 599 * @param controlObject The JSON object to be decoded. It must not be 600 * {@code null}. 601 * @param strict Indicates whether to use strict mode when decoding 602 * the provided JSON object. If this is {@code true}, 603 * then this method will throw an exception if the 604 * provided JSON object contains any unrecognized 605 * fields. If this is {@code false}, then unrecognized 606 * fields will be ignored. 607 * 608 * @return The generate access token response control that was decoded from 609 * the provided JSON object. 610 * 611 * @throws LDAPException If the provided JSON object cannot be parsed as a 612 * valid generate access token response control. 613 */ 614 @NotNull() 615 public static GenerateAccessTokenResponseControl decodeJSONControl( 616 @NotNull final JSONObject controlObject, 617 final boolean strict) 618 throws LDAPException 619 { 620 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 621 controlObject, strict, true, true); 622 623 final ASN1OctetString rawValue = jsonControl.getRawValue(); 624 if (rawValue != null) 625 { 626 return new GenerateAccessTokenResponseControl(jsonControl.getOID(), 627 jsonControl.getCriticality(), rawValue); 628 } 629 630 631 final JSONObject valueObject = jsonControl.getValueObject(); 632 final ObjectTrio<String,Date,String> valueElements = 633 decodeJSONObject(valueObject); 634 635 if (strict) 636 { 637 final List<String> unrecognizedFields = 638 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 639 valueObject, JSON_FIELD_ACCESS_TOKEN, 640 JSON_FIELD_EXPIRATION_TIME, JSON_FIELD_ERROR_MESSAGE); 641 if (! unrecognizedFields.isEmpty()) 642 { 643 throw new LDAPException(ResultCode.DECODING_ERROR, 644 ERR_GENERATE_TOKEN_RESPONSE_JSON_CONTROL_UNRECOGNIZED_FIELD.get( 645 controlObject.toSingleLineString(), 646 unrecognizedFields.get(0))); 647 } 648 } 649 650 651 return new GenerateAccessTokenResponseControl(valueElements.getFirst(), 652 valueElements.getSecond(), valueElements.getThird()); 653 } 654 655 656 657 /** 658 * {@inheritDoc} 659 */ 660 @Override() 661 public void toString(@NotNull final StringBuilder buffer) 662 { 663 buffer.append("GenerateAccessTokenResponseControl(hasAccessToken="); 664 buffer.append(accesToken != null); 665 666 if (expirationTime != null) 667 { 668 buffer.append(", expirationTime='"); 669 buffer.append(StaticUtils.encodeRFC3339Time(expirationTime)); 670 buffer.append('\''); 671 } 672 673 if (errorMessage != null) 674 { 675 buffer.append(", errorMessage='"); 676 buffer.append(errorMessage); 677 buffer.append('\''); 678 } 679 680 buffer.append(')'); 681 } 682}