001/* 002 * Copyright 2008-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2022 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) 2008-2022 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.extensions; 037 038 039 040import java.util.ArrayList; 041 042import com.unboundid.asn1.ASN1Element; 043import com.unboundid.asn1.ASN1OctetString; 044import com.unboundid.asn1.ASN1Sequence; 045import com.unboundid.ldap.sdk.Control; 046import com.unboundid.ldap.sdk.ExtendedRequest; 047import com.unboundid.ldap.sdk.ExtendedResult; 048import com.unboundid.ldap.sdk.LDAPConnection; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.NotNull; 054import com.unboundid.util.Nullable; 055import com.unboundid.util.StaticUtils; 056import com.unboundid.util.ThreadSafety; 057import com.unboundid.util.ThreadSafetyLevel; 058 059import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*; 060 061 062 063/** 064 * This class provides an implementation of the LDAP password modify extended 065 * request as defined in 066 * <A HREF="http://www.ietf.org/rfc/rfc3062.txt">RFC 3062</A>. It may be used 067 * to change the password for a user in the directory, and provides the ability 068 * to specify the current password for verification. It also offers the ability 069 * to request that the server generate a new password for the user. 070 * <BR><BR> 071 * The elements of a password modify extended request include: 072 * <UL> 073 * <LI>{@code userIdentity} -- This specifies the user for which to change the 074 * password. It should generally be the DN for the target user (although 075 * the specification does indicate that some servers may accept other 076 * values). If no value is provided, then the server will attempt to 077 * change the password for the currently-authenticated user.</LI> 078 * <LI>{@code oldPassword} -- This specifies the current password for the 079 * user. Some servers may require that the old password be provided when 080 * a user is changing his or her own password as an extra level of 081 * verification, but it is generally not necessary when an administrator 082 * is resetting the password for another user.</LI> 083 * <LI>{@code newPassword} -- This specifies the new password to use for the 084 * user. If it is not provided, then the server may attempt to generate a 085 * new password for the user, and in that case it will be included in the 086 * {@code generatedPassword} field of the corresponding 087 * {@link PasswordModifyExtendedResult}. Note that some servers may not 088 * support generating a new password, in which case the client will always 089 * be required to provide it.</LI> 090 * </UL> 091 * <H2>Example</H2> 092 * The following example demonstrates the use of the password modify extended 093 * operation to change the password for user 094 * "uid=test.user,ou=People,dc=example,dc=com". Neither the current password 095 * nor a new password will be provided, so the server will generate a new 096 * password for the user. 097 * <PRE> 098 * PasswordModifyExtendedRequest passwordModifyRequest = 099 * new PasswordModifyExtendedRequest( 100 * "uid=test.user,ou=People,dc=example,dc=com", // The user to update 101 * (String) null, // The current password for the user. 102 * (String) null); // The new password. null = server will generate 103 * 104 * PasswordModifyExtendedResult passwordModifyResult; 105 * try 106 * { 107 * passwordModifyResult = (PasswordModifyExtendedResult) 108 * connection.processExtendedOperation(passwordModifyRequest); 109 * // This doesn't necessarily mean that the operation was successful, since 110 * // some kinds of extended operations return non-success results under 111 * // normal conditions. 112 * } 113 * catch (LDAPException le) 114 * { 115 * // For an extended operation, this generally means that a problem was 116 * // encountered while trying to send the request or read the result. 117 * passwordModifyResult = new PasswordModifyExtendedResult( 118 * new ExtendedResult(le)); 119 * } 120 * 121 * LDAPTestUtils.assertResultCodeEquals(passwordModifyResult, 122 * ResultCode.SUCCESS); 123 * String serverGeneratedNewPassword = 124 * passwordModifyResult.getGeneratedPassword(); 125 * </PRE> 126 */ 127@NotMutable() 128@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 129public final class PasswordModifyExtendedRequest 130 extends ExtendedRequest 131{ 132 /** 133 * The OID (1.3.6.1.4.1.4203.1.11.1) for the password modify extended request. 134 */ 135 @NotNull public static final String PASSWORD_MODIFY_REQUEST_OID = 136 "1.3.6.1.4.1.4203.1.11.1"; 137 138 139 140 /** 141 * The BER type for the user identity element. 142 */ 143 private static final byte TYPE_USER_IDENTITY = (byte) 0x80; 144 145 146 147 /** 148 * The BER type for the old password element. 149 */ 150 private static final byte TYPE_OLD_PASSWORD = (byte) 0x81; 151 152 153 154 /** 155 * The BER type for the new password element. 156 */ 157 private static final byte TYPE_NEW_PASSWORD = (byte) 0x82; 158 159 160 161 /** 162 * The serial version UID for this serializable class. 163 */ 164 private static final long serialVersionUID = 4965048727456933570L; 165 166 167 168 // The old password for this request. 169 @Nullable private final ASN1OctetString oldPassword; 170 171 // The new password for this request. 172 @Nullable private final ASN1OctetString newPassword; 173 174 // The user identity string for this request. 175 @Nullable private final String userIdentity; 176 177 178 179 /** 180 * Creates a new password modify extended request that will attempt to change 181 * the password of the currently-authenticated user. 182 * 183 * @param newPassword The new password for the user. It may be {@code null} 184 * if the new password should be generated by the 185 * directory server. 186 */ 187 public PasswordModifyExtendedRequest(@Nullable final String newPassword) 188 { 189 this(null, null, newPassword, null); 190 } 191 192 193 194 /** 195 * Creates a new password modify extended request that will attempt to change 196 * the password of the currently-authenticated user. 197 * 198 * @param newPassword The new password for the user. It may be {@code null} 199 * if the new password should be generated by the 200 * directory server. 201 */ 202 public PasswordModifyExtendedRequest(@Nullable final byte[] newPassword) 203 { 204 this(null, null, newPassword, null); 205 } 206 207 208 209 /** 210 * Creates a new password modify extended request that will attempt to change 211 * the password of the currently-authenticated user. 212 * 213 * @param oldPassword The current password for the user. It may be 214 * {@code null} if the directory server does not require 215 * the user's current password for self changes. 216 * @param newPassword The new password for the user. It may be {@code null} 217 * if the new password should be generated by the 218 * directory server. 219 */ 220 public PasswordModifyExtendedRequest(@Nullable final String oldPassword, 221 @Nullable final String newPassword) 222 { 223 this(null, oldPassword, newPassword, null); 224 } 225 226 227 228 /** 229 * Creates a new password modify extended request that will attempt to change 230 * the password of the currently-authenticated user. 231 * 232 * @param oldPassword The current password for the user. It may be 233 * {@code null} if the directory server does not require 234 * the user's current password for self changes. 235 * @param newPassword The new password for the user. It may be {@code null} 236 * if the new password should be generated by the 237 * directory server. 238 */ 239 public PasswordModifyExtendedRequest(@Nullable final byte[] oldPassword, 240 @Nullable final byte[] newPassword) 241 { 242 this(null, oldPassword, newPassword, null); 243 } 244 245 246 247 /** 248 * Creates a new password modify extended request that will attempt to change 249 * the password for the specified user. 250 * 251 * @param userIdentity The string that identifies the user whose password 252 * should be changed. It may or may not be a DN, but if 253 * it is not a DN, then the directory server must be 254 * able to identify the appropriate user from the 255 * provided identifier. It may be {@code null} to 256 * indicate that the password change should be for the 257 * currently-authenticated user. 258 * @param oldPassword The current password for the user. It may be 259 * {@code null} if the directory server does not require 260 * the user's current password for self changes. 261 * @param newPassword The new password for the user. It may be 262 * {@code null} if the new password should be generated 263 * by the directory server. 264 */ 265 public PasswordModifyExtendedRequest(@Nullable final String userIdentity, 266 @Nullable final String oldPassword, 267 @Nullable final String newPassword) 268 { 269 this(userIdentity, oldPassword, newPassword, null); 270 } 271 272 273 274 /** 275 * Creates a new password modify extended request that will attempt to change 276 * the password for the specified user. 277 * 278 * @param userIdentity The string that identifies the user whose password 279 * should be changed. It may or may not be a DN, but if 280 * it is not a DN, then the directory server must be 281 * able to identify the appropriate user from the 282 * provided identifier. It may be {@code null} to 283 * indicate that the password change should be for the 284 * currently-authenticated user. 285 * @param oldPassword The current password for the user. It may be 286 * {@code null} if the directory server does not require 287 * the user's current password for self changes. 288 * @param newPassword The new password for the user. It may be 289 * {@code null} if the new password should be generated 290 * by the directory server. 291 */ 292 public PasswordModifyExtendedRequest(@Nullable final String userIdentity, 293 @Nullable final byte[] oldPassword, 294 @Nullable final byte[] newPassword) 295 { 296 this(userIdentity, oldPassword, newPassword, null); 297 } 298 299 300 301 /** 302 * Creates a new password modify extended request that will attempt to change 303 * the password for the specified user. 304 * 305 * @param userIdentity The string that identifies the user whose password 306 * should be changed. It may or may not be a DN, but if 307 * it is not a DN, then the directory server must be 308 * able to identify the appropriate user from the 309 * provided identifier. It may be {@code null} to 310 * indicate that the password change should be for the 311 * currently-authenticated user. 312 * @param oldPassword The current password for the user. It may be 313 * {@code null} if the directory server does not require 314 * the user's current password for self changes. 315 * @param newPassword The new password for the user. It may be 316 * {@code null} if the new password should be generated 317 * by the directory server. 318 * @param controls The set of controls to include in the request. 319 */ 320 public PasswordModifyExtendedRequest(@Nullable final String userIdentity, 321 @Nullable final String oldPassword, 322 @Nullable final String newPassword, 323 @Nullable final Control[] controls) 324 { 325 super(PASSWORD_MODIFY_REQUEST_OID, 326 encodeValue(userIdentity, oldPassword, newPassword), controls); 327 328 this.userIdentity = userIdentity; 329 330 if (oldPassword == null) 331 { 332 this.oldPassword = null; 333 } 334 else 335 { 336 this.oldPassword = new ASN1OctetString(TYPE_OLD_PASSWORD, oldPassword); 337 } 338 339 if (newPassword == null) 340 { 341 this.newPassword = null; 342 } 343 else 344 { 345 this.newPassword = new ASN1OctetString(TYPE_NEW_PASSWORD, newPassword); 346 } 347 } 348 349 350 351 /** 352 * Creates a new password modify extended request that will attempt to change 353 * the password for the specified user. 354 * 355 * @param userIdentity The string that identifies the user whose password 356 * should be changed. It may or may not be a DN, but if 357 * it is not a DN, then the directory server must be 358 * able to identify the appropriate user from the 359 * provided identifier. It may be {@code null} to 360 * indicate that the password change should be for the 361 * currently-authenticated user. 362 * @param oldPassword The current password for the user. It may be 363 * {@code null} if the directory server does not require 364 * the user's current password for self changes. 365 * @param newPassword The new password for the user. It may be 366 * {@code null} if the new password should be generated 367 * by the directory server. 368 * @param controls The set of controls to include in the request. 369 */ 370 public PasswordModifyExtendedRequest(@Nullable final String userIdentity, 371 @Nullable final byte[] oldPassword, 372 @Nullable final byte[] newPassword, 373 @Nullable final Control[] controls) 374 { 375 super(PASSWORD_MODIFY_REQUEST_OID, 376 encodeValue(userIdentity, oldPassword, newPassword), controls); 377 378 this.userIdentity = userIdentity; 379 380 if (oldPassword == null) 381 { 382 this.oldPassword = null; 383 } 384 else 385 { 386 this.oldPassword = new ASN1OctetString(TYPE_OLD_PASSWORD, oldPassword); 387 } 388 389 if (newPassword == null) 390 { 391 this.newPassword = null; 392 } 393 else 394 { 395 this.newPassword = new ASN1OctetString(TYPE_NEW_PASSWORD, newPassword); 396 } 397 } 398 399 400 401 /** 402 * Creates a new password modify extended request from the provided generic 403 * extended request. 404 * 405 * @param extendedRequest The generic extended request to use to create this 406 * password modify extended request. 407 * 408 * @throws LDAPException If a problem occurs while decoding the request. 409 */ 410 public PasswordModifyExtendedRequest( 411 @NotNull final ExtendedRequest extendedRequest) 412 throws LDAPException 413 { 414 super(extendedRequest); 415 416 final ASN1OctetString value = extendedRequest.getValue(); 417 if (value == null) 418 { 419 throw new LDAPException(ResultCode.DECODING_ERROR, 420 ERR_PW_MODIFY_REQUEST_NO_VALUE.get()); 421 } 422 423 try 424 { 425 ASN1OctetString oldPW = null; 426 ASN1OctetString newPW = null; 427 String userID = null; 428 429 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 430 final ASN1Element[] elements = 431 ASN1Sequence.decodeAsSequence(valueElement).elements(); 432 for (final ASN1Element e : elements) 433 { 434 switch (e.getType()) 435 { 436 case TYPE_USER_IDENTITY: 437 userID = ASN1OctetString.decodeAsOctetString(e).stringValue(); 438 break; 439 440 case TYPE_OLD_PASSWORD: 441 oldPW = ASN1OctetString.decodeAsOctetString(e); 442 break; 443 444 case TYPE_NEW_PASSWORD: 445 newPW = ASN1OctetString.decodeAsOctetString(e); 446 break; 447 448 default: 449 throw new LDAPException(ResultCode.DECODING_ERROR, 450 ERR_PW_MODIFY_REQUEST_INVALID_TYPE.get( 451 StaticUtils.toHex(e.getType()))); 452 } 453 } 454 455 userIdentity = userID; 456 oldPassword = oldPW; 457 newPassword = newPW; 458 } 459 catch (final LDAPException le) 460 { 461 Debug.debugException(le); 462 throw le; 463 } 464 catch (final Exception e) 465 { 466 Debug.debugException(e); 467 throw new LDAPException(ResultCode.DECODING_ERROR, 468 ERR_PW_MODIFY_REQUEST_CANNOT_DECODE.get(e), e); 469 } 470 } 471 472 473 474 /** 475 * Encodes the provided information into an ASN.1 octet string suitable for 476 * use as the value of this extended request. 477 * 478 * @param userIdentity The string that identifies the user whose password 479 * should be changed. It may or may not be a DN, but if 480 * it is not a DN, then the directory server must be 481 * able to identify the appropriate user from the 482 * provided identifier. It may be {@code null} to 483 * indicate that the password change should be for the 484 * currently-authenticated user. 485 * @param oldPassword The current password for the user. It may be 486 * {@code null} if the directory server does not require 487 * the user's current password for self changes. 488 * @param newPassword The new password for the user. It may be 489 * {@code null} if the new password should be generated 490 * by the directory server. 491 * 492 * @return The ASN.1 octet string containing the encoded value. 493 */ 494 @NotNull() 495 private static ASN1OctetString encodeValue( 496 @Nullable final String userIdentity, 497 @Nullable final String oldPassword, 498 @Nullable final String newPassword) 499 { 500 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 501 502 if (userIdentity != null) 503 { 504 elements.add(new ASN1OctetString(TYPE_USER_IDENTITY, userIdentity)); 505 } 506 507 if (oldPassword != null) 508 { 509 elements.add(new ASN1OctetString(TYPE_OLD_PASSWORD, oldPassword)); 510 } 511 512 if (newPassword != null) 513 { 514 elements.add(new ASN1OctetString(TYPE_NEW_PASSWORD, newPassword)); 515 } 516 517 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 518 } 519 520 521 522 /** 523 * Encodes the provided information into an ASN.1 octet string suitable for 524 * use as the value of this extended request. 525 * 526 * @param userIdentity The string that identifies the user whose password 527 * should be changed. It may or may not be a DN, but if 528 * it is not a DN, then the directory server must be 529 * able to identify the appropriate user from the 530 * provided identifier. It may be {@code null} to 531 * indicate that the password change should be for the 532 * currently-authenticated user. 533 * @param oldPassword The current password for the user. It may be 534 * {@code null} if the directory server does not require 535 * the user's current password for self changes. 536 * @param newPassword The new password for the user. It may be 537 * {@code null} if the new password should be generated 538 * by the directory server. 539 * 540 * @return The ASN.1 octet string containing the encoded value. 541 */ 542 @NotNull() 543 private static ASN1OctetString encodeValue( 544 @Nullable final String userIdentity, 545 @Nullable final byte[] oldPassword, 546 @Nullable final byte[] newPassword) 547 { 548 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 549 550 if (userIdentity != null) 551 { 552 elements.add(new ASN1OctetString(TYPE_USER_IDENTITY, userIdentity)); 553 } 554 555 if (oldPassword != null) 556 { 557 elements.add(new ASN1OctetString(TYPE_OLD_PASSWORD, oldPassword)); 558 } 559 560 if (newPassword != null) 561 { 562 elements.add(new ASN1OctetString(TYPE_NEW_PASSWORD, newPassword)); 563 } 564 565 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 566 } 567 568 569 570 /** 571 * Retrieves the user identity for this request, if available. 572 * 573 * @return The user identity for this request, or {@code null} if the 574 * password change should target the currently-authenticated user. 575 */ 576 @Nullable() 577 public String getUserIdentity() 578 { 579 return userIdentity; 580 } 581 582 583 584 /** 585 * Retrieves the string representation of the old password for this request, 586 * if available. 587 * 588 * @return The string representation of the old password for this request, or 589 * {@code null} if it was not provided. 590 */ 591 @Nullable() 592 public String getOldPassword() 593 { 594 if (oldPassword == null) 595 { 596 return null; 597 } 598 else 599 { 600 return oldPassword.stringValue(); 601 } 602 } 603 604 605 606 /** 607 * Retrieves the binary representation of the old password for this request, 608 * if available. 609 * 610 * @return The binary representation of the old password for this request, or 611 * {@code null} if it was not provided. 612 */ 613 @Nullable() 614 public byte[] getOldPasswordBytes() 615 { 616 if (oldPassword == null) 617 { 618 return null; 619 } 620 else 621 { 622 return oldPassword.getValue(); 623 } 624 } 625 626 627 628 /** 629 * Retrieves the raw old password for this request, if available. 630 * 631 * @return The raw old password for this request, or {@code null} if it was 632 * not provided. 633 */ 634 @Nullable() 635 public ASN1OctetString getRawOldPassword() 636 { 637 return oldPassword; 638 } 639 640 641 642 /** 643 * Retrieves the string representation of the new password for this request, 644 * if available. 645 * 646 * @return The string representation of the new password for this request, or 647 * {@code null} if it was not provided. 648 */ 649 @Nullable() 650 public String getNewPassword() 651 { 652 if (newPassword == null) 653 { 654 return null; 655 } 656 else 657 { 658 return newPassword.stringValue(); 659 } 660 } 661 662 663 664 /** 665 * Retrieves the binary representation of the new password for this request, 666 * if available. 667 * 668 * @return The binary representation of the new password for this request, or 669 * {@code null} if it was not provided. 670 */ 671 @Nullable() 672 public byte[] getNewPasswordBytes() 673 { 674 if (newPassword == null) 675 { 676 return null; 677 } 678 else 679 { 680 return newPassword.getValue(); 681 } 682 } 683 684 685 686 /** 687 * Retrieves the raw new password for this request, if available. 688 * 689 * @return The raw new password for this request, or {@code null} if it was 690 * not provided. 691 */ 692 @Nullable() 693 public ASN1OctetString getRawNewPassword() 694 { 695 return newPassword; 696 } 697 698 699 700 /** 701 * {@inheritDoc} 702 */ 703 @Override() 704 @NotNull() 705 public PasswordModifyExtendedResult process( 706 @NotNull final LDAPConnection connection, final int depth) 707 throws LDAPException 708 { 709 final ExtendedResult extendedResponse = super.process(connection, depth); 710 return new PasswordModifyExtendedResult(extendedResponse); 711 } 712 713 714 715 /** 716 * {@inheritDoc} 717 */ 718 @Override() 719 @NotNull() 720 public PasswordModifyExtendedRequest duplicate() 721 { 722 return duplicate(getControls()); 723 } 724 725 726 727 /** 728 * {@inheritDoc} 729 */ 730 @Override() 731 @NotNull() 732 public PasswordModifyExtendedRequest duplicate( 733 @Nullable final Control[] controls) 734 { 735 final byte[] oldPWBytes = 736 (oldPassword == null) ? null : oldPassword.getValue(); 737 final byte[] newPWBytes = 738 (newPassword == null) ? null : newPassword.getValue(); 739 740 final PasswordModifyExtendedRequest r = 741 new PasswordModifyExtendedRequest(userIdentity, oldPWBytes, 742 newPWBytes, controls); 743 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 744 return r; 745 } 746 747 748 749 /** 750 * {@inheritDoc} 751 */ 752 @Override() 753 @NotNull() 754 public String getExtendedRequestName() 755 { 756 return INFO_EXTENDED_REQUEST_NAME_PASSWORD_MODIFY.get(); 757 } 758 759 760 761 /** 762 * {@inheritDoc} 763 */ 764 @Override() 765 public void toString(@NotNull final StringBuilder buffer) 766 { 767 buffer.append("PasswordModifyExtendedRequest("); 768 769 boolean dataAdded = false; 770 771 if (userIdentity != null) 772 { 773 buffer.append("userIdentity='"); 774 buffer.append(userIdentity); 775 buffer.append('\''); 776 dataAdded = true; 777 } 778 779 if (oldPassword != null) 780 { 781 if (dataAdded) 782 { 783 buffer.append(", "); 784 } 785 786 buffer.append("oldPassword='"); 787 buffer.append(oldPassword.stringValue()); 788 buffer.append('\''); 789 dataAdded = true; 790 } 791 792 if (newPassword != null) 793 { 794 if (dataAdded) 795 { 796 buffer.append(", "); 797 } 798 799 buffer.append("newPassword='"); 800 buffer.append(newPassword.stringValue()); 801 buffer.append('\''); 802 dataAdded = true; 803 } 804 805 final Control[] controls = getControls(); 806 if (controls.length > 0) 807 { 808 if (dataAdded) 809 { 810 buffer.append(", "); 811 } 812 813 buffer.append("controls={"); 814 for (int i=0; i < controls.length; i++) 815 { 816 if (i > 0) 817 { 818 buffer.append(", "); 819 } 820 821 buffer.append(controls[i]); 822 } 823 buffer.append('}'); 824 } 825 826 buffer.append(')'); 827 } 828}