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