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