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