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