001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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=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)
112    public 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    }