001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.LinkedHashSet;
022import java.util.List;
023
024import org.forgerock.i18n.LocalizableMessage;
025import org.forgerock.i18n.LocalizedIllegalArgumentException;
026import org.forgerock.i18n.slf4j.LocalizedLogger;
027import org.forgerock.opendj.config.server.ConfigException;
028import org.forgerock.opendj.io.ASN1;
029import org.forgerock.opendj.io.ASN1Reader;
030import org.forgerock.opendj.io.ASN1Writer;
031import org.forgerock.opendj.ldap.ByteString;
032import org.forgerock.opendj.ldap.ByteStringBuilder;
033import org.forgerock.opendj.ldap.DN;
034import org.forgerock.opendj.ldap.GeneralizedTime;
035import org.forgerock.opendj.ldap.ResultCode;
036import org.forgerock.opendj.ldap.SearchScope;
037import org.opends.server.admin.std.server.PasswordPolicyStateExtendedOperationHandlerCfg;
038import org.opends.server.api.AuthenticationPolicy;
039import org.opends.server.api.ClientConnection;
040import org.opends.server.api.ExtendedOperationHandler;
041import org.opends.server.core.*;
042import org.opends.server.protocols.internal.InternalClientConnection;
043import org.opends.server.protocols.internal.InternalSearchOperation;
044import org.opends.server.protocols.internal.SearchRequest;
045import org.opends.server.schema.GeneralizedTimeSyntax;
046import org.opends.server.types.*;
047
048import static org.opends.messages.CoreMessages.*;
049import static org.opends.messages.ExtensionMessages.*;
050import static org.opends.server.protocols.internal.Requests.*;
051import static org.opends.server.util.CollectionUtils.*;
052import static org.opends.server.util.ServerConstants.*;
053import static org.opends.server.util.StaticUtils.*;
054
055/**
056 * This class implements an LDAP extended operation that can be used to query
057 * and update elements of the Directory Server password policy state for a given
058 * user.  The ASN.1 definition for the value of the extended request is:
059 * <BR>
060 * <PRE>
061 * PasswordPolicyStateValue ::= SEQUENCE {
062 *      targetUser     LDAPDN
063 *      operations     SEQUENCE OF PasswordPolicyStateOperation OPTIONAL }
064 *
065 * PasswordPolicyStateOperation ::= SEQUENCE {
066 *      opType       ENUMERATED {
067 *           getPasswordPolicyDN                          (0),
068 *           getAccountDisabledState                      (1),
069 *           setAccountDisabledState                      (2),
070 *           clearAccountDisabledState                    (3),
071 *           getAccountExpirationTime                     (4),
072 *           setAccountExpirationTime                     (5),
073 *           clearAccountExpirationTime                   (6),
074 *           getSecondsUntilAccountExpiration             (7),
075 *           getPasswordChangedTime                       (8),
076 *           setPasswordChangedTime                       (9),
077 *           clearPasswordChangedTime                     (10),
078 *           getPasswordExpirationWarnedTime              (11),
079 *           setPasswordExpirationWarnedTime              (12),
080 *           clearPasswordExpirationWarnedTime            (13),
081 *           getSecondsUntilPasswordExpiration            (14),
082 *           getSecondsUntilPasswordExpirationWarning     (15),
083 *           getAuthenticationFailureTimes                (16),
084 *           addAuthenticationFailureTime                 (17),
085 *           setAuthenticationFailureTimes                (18),
086 *           clearAuthenticationFailureTimes              (19),
087 *           getSecondsUntilAuthenticationFailureUnlock   (20),
088 *           getRemainingAuthenticationFailureCount       (21),
089 *           getLastLoginTime                             (22),
090 *           setLastLoginTime                             (23),
091 *           clearLastLoginTime                           (24),
092 *           getSecondsUntilIdleLockout                   (25),
093 *           getPasswordResetState                        (26),
094 *           setPasswordResetState                        (27),
095 *           clearPasswordResetState                      (28),
096 *           getSecondsUntilPasswordResetLockout          (29),
097 *           getGraceLoginUseTimes                        (30),
098 *           addGraceLoginUseTime                         (31),
099 *           setGraceLoginUseTimes                        (32),
100 *           clearGraceLoginUseTimes                      (33),
101 *           getRemainingGraceLoginCount                  (34),
102 *           getPasswordChangedByRequiredTime             (35),
103 *           setPasswordChangedByRequiredTime             (36),
104 *           clearPasswordChangedByRequiredTime           (37),
105 *           getSecondsUntilRequiredChangeTime            (38),
106 *           getPasswordHistory                           (39),
107 *           clearPasswordHistory                         (40),
108 *           ... },
109 *      opValues     SEQUENCE OF OCTET STRING OPTIONAL }
110 * </PRE>
111 * <BR>
112 * Both the request and response values use the same encoded form, and they both
113 * use the same OID of "1.3.6.1.4.1.26027.1.6.1".  The response value will only
114 * include get* elements.  If the request did not include any operations, then
115 * the response will include all get* elements; otherwise, the response will
116 * only include the get* elements that correspond to the state fields referenced
117 * in the request (regardless of whether that operation was included in a get*,
118 * set*, add*, remove*, or clear* operation).
119 */
120public class PasswordPolicyStateExtendedOperation
121       extends ExtendedOperationHandler<
122                    PasswordPolicyStateExtendedOperationHandlerCfg>
123{
124  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
125
126
127  /** The enumerated value for the getPasswordPolicyDN operation. */
128  public static final int OP_GET_PASSWORD_POLICY_DN = 0;
129  /** The enumerated value for the getAccountDisabledState operation. */
130  public static final int OP_GET_ACCOUNT_DISABLED_STATE = 1;
131  /** The enumerated value for the setAccountDisabledState operation. */
132  public static final int OP_SET_ACCOUNT_DISABLED_STATE = 2;
133  /** The enumerated value for the clearAccountDisabledState operation. */
134  public static final int OP_CLEAR_ACCOUNT_DISABLED_STATE = 3;
135  /** The enumerated value for the getAccountExpirationTime operation. */
136  public static final int OP_GET_ACCOUNT_EXPIRATION_TIME = 4;
137  /** The enumerated value for the setAccountExpirationTime operation. */
138  public static final int OP_SET_ACCOUNT_EXPIRATION_TIME = 5;
139  /** The enumerated value for the clearAccountExpirationTime operation. */
140  public static final int OP_CLEAR_ACCOUNT_EXPIRATION_TIME = 6;
141  /**
142   * The enumerated value for the getSecondsUntilAccountExpiration operation.
143   */
144  public static final int OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION = 7;
145  /** The enumerated value for the getPasswordChangedTime operation. */
146  public static final int OP_GET_PASSWORD_CHANGED_TIME = 8;
147  /** The enumerated value for the setPasswordChangedTime operation. */
148  public static final int OP_SET_PASSWORD_CHANGED_TIME = 9;
149  /** The enumerated value for the clearPasswordChangedTime operation. */
150  public static final int OP_CLEAR_PASSWORD_CHANGED_TIME = 10;
151  /** The enumerated value for the getPasswordExpirationWarnedTime operation. */
152  public static final int OP_GET_PASSWORD_EXPIRATION_WARNED_TIME = 11;
153  /** The enumerated value for the setPasswordExpirationWarnedTime operation. */
154  public static final int OP_SET_PASSWORD_EXPIRATION_WARNED_TIME = 12;
155  /**
156   * The enumerated value for the clearPasswordExpirationWarnedTime operation.
157   */
158  public static final int OP_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME = 13;
159  /**
160   * The enumerated value for the getSecondsUntilPasswordExpiration operation.
161   */
162  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION = 14;
163  /**
164   * The enumerated value for the getSecondsUntilPasswordExpirationWarning
165   * operation.
166   */
167  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING = 15;
168  /** The enumerated value for the getAuthenticationFailureTimes operation. */
169  public static final int OP_GET_AUTHENTICATION_FAILURE_TIMES = 16;
170  /** The enumerated value for the addAuthenticationFailureTime operation. */
171  public static final int OP_ADD_AUTHENTICATION_FAILURE_TIME = 17;
172  /** The enumerated value for the setAuthenticationFailureTimes operation. */
173  public static final int OP_SET_AUTHENTICATION_FAILURE_TIMES = 18;
174  /** The enumerated value for the clearAuthenticationFailureTimes operation. */
175  public static final int OP_CLEAR_AUTHENTICATION_FAILURE_TIMES = 19;
176  /**
177   * The enumerated value for the getSecondsUntilAuthenticationFailureUnlock
178   * operation.
179   */
180  public static final int OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK =
181       20;
182  /**
183   * The enumerated value for the getRemainingAuthenticationFailureCount
184   * operation.
185   */
186  public static final int OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT = 21;
187  /** The enumerated value for the getLastLoginTime operation. */
188  public static final int OP_GET_LAST_LOGIN_TIME = 22;
189  /** The enumerated value for the setLastLoginTime operation. */
190  public static final int OP_SET_LAST_LOGIN_TIME = 23;
191  /** The enumerated value for the clearLastLoginTime operation. */
192  public static final int OP_CLEAR_LAST_LOGIN_TIME = 24;
193  /** The enumerated value for the getSecondsUntilIdleLockout operation. */
194  public static final int OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT = 25;
195  /** The enumerated value for the getPasswordResetState operation. */
196  public static final int OP_GET_PASSWORD_RESET_STATE = 26;
197  /** The enumerated value for the setPasswordResetState operation. */
198  public static final int OP_SET_PASSWORD_RESET_STATE = 27;
199  /** The enumerated value for the clearPasswordResetState operation. */
200  public static final int OP_CLEAR_PASSWORD_RESET_STATE = 28;
201  /**
202   * The enumerated value for the getSecondsUntilPasswordResetLockout operation.
203   */
204  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT = 29;
205  /** The enumerated value for the getGraceLoginUseTimes operation. */
206  public static final int OP_GET_GRACE_LOGIN_USE_TIMES = 30;
207  /** The enumerated value for the addGraceLoginUseTime operation. */
208  public static final int OP_ADD_GRACE_LOGIN_USE_TIME = 31;
209  /** The enumerated value for the setGraceLoginUseTimes operation. */
210  public static final int OP_SET_GRACE_LOGIN_USE_TIMES = 32;
211  /** The enumerated value for the clearGraceLoginUseTimes operation. */
212  public static final int OP_CLEAR_GRACE_LOGIN_USE_TIMES = 33;
213  /** The enumerated value for the getRemainingGraceLoginCount operation. */
214  public static final int OP_GET_REMAINING_GRACE_LOGIN_COUNT = 34;
215  /**
216   * The enumerated value for the getPasswordChangedByRequiredTime operation.
217   */
218  public static final int OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME = 35;
219  /**
220   * The enumerated value for the setPasswordChangedByRequiredTime operation.
221   */
222  public static final int OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME = 36;
223  /**
224   * The enumerated value for the clearPasswordChangedByRequiredTime operation.
225   */
226  public static final int OP_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME = 37;
227  /**
228   * The enumerated value for the getSecondsUntilRequiredChangeTime operation.
229   */
230  public static final int OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME = 38;
231  /** The enumerated value for the getPasswordHistory operation. */
232  public static final int OP_GET_PASSWORD_HISTORY = 39;
233  /** The enumerated value for the clearPasswordHistory operation. */
234  public static final int OP_CLEAR_PASSWORD_HISTORY = 40;
235
236
237  /** The set of attributes to request when retrieving a user's entry. */
238  private LinkedHashSet<String> requestAttributes;
239
240  /** The search filter that will be used to retrieve user entries. */
241  private SearchFilter userFilter;
242
243  private boolean isAccountSetDisabled;
244  private boolean isAccountSetEnabled;
245
246  /**
247   * Create an instance of this password policy state extended operation.  All
248   * initialization should be performed in the
249   * {@code initializeExtendedOperationHandler} method.
250   */
251  public PasswordPolicyStateExtendedOperation()
252  {
253    super();
254  }
255
256
257  /**
258   * Initializes this extended operation handler based on the information in the
259   * provided configuration entry.  It should also register itself with the
260   * Directory Server for the particular kinds of extended operations that it
261   * will process.
262   *
263   * @param  config       The configuration that contains the information
264   *                      to use to initialize this extended operation handler.
265   *
266   * @throws  ConfigException  If an unrecoverable problem arises in the
267   *                           process of performing the initialization.
268   *
269   * @throws  InitializationException  If a problem occurs during initialization
270   *                                   that is not related to the server
271   *                                   configuration.
272   */
273  @Override
274  public void initializeExtendedOperationHandler(
275                   PasswordPolicyStateExtendedOperationHandlerCfg config)
276         throws ConfigException, InitializationException
277  {
278    userFilter = SearchFilter.objectClassPresent();
279    requestAttributes = newLinkedHashSet("*", "+");
280
281    DirectoryServer.registerSupportedExtension(OID_PASSWORD_POLICY_STATE_EXTOP, this);
282    // FIXME registerControlAndFeatures?
283  }
284
285  /**
286   * Processes the provided extended operation.
287   *
288   * @param  operation  The extended operation to be processed.
289   */
290  @Override
291  public void processExtendedOperation(ExtendedOperation operation)
292  {
293    operation.setResultCode(ResultCode.UNDEFINED);
294
295
296    // The user must have the password-reset privilege in order to be able to do
297    // anything with this extended operation.
298    ClientConnection clientConnection = operation.getClientConnection();
299    if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, operation))
300    {
301      LocalizableMessage message = ERR_PWPSTATE_EXTOP_NO_PRIVILEGE.get();
302      operation.appendErrorMessage(message);
303      operation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
304      return;
305    }
306
307
308    // There must be a request value, and it must be a sequence.  Decode it
309    // into its components.
310    ByteString requestValue = operation.getRequestValue();
311    if (requestValue == null)
312    {
313      LocalizableMessage message = ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE.get();
314      operation.appendErrorMessage(message);
315      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
316      return;
317    }
318
319    ByteString dnString;
320    ASN1Reader reader = ASN1.getReader(requestValue);
321    try
322    {
323      reader.readStartSequence();
324      dnString   = reader.readOctetString();
325    }
326    catch (Exception e)
327    {
328      logger.traceException(e);
329
330      LocalizableMessage message =
331          ERR_PWPSTATE_EXTOP_DECODE_FAILURE.get(getExceptionMessage(e));
332      operation.appendErrorMessage(message);
333      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
334      return;
335    }
336
337
338    // Decode the DN and get the corresponding user entry.
339    DN targetDN;
340    try
341    {
342      targetDN = DN.valueOf(dnString);
343    }
344    catch (LocalizedIllegalArgumentException e)
345    {
346      logger.traceException(e);
347
348      operation.setResultCode(ResultCode.INVALID_DN_SYNTAX);
349      operation.appendErrorMessage(e.getMessageObject());
350      return;
351    }
352
353    DN rootDN = DirectoryServer.getActualRootBindDN(targetDN);
354    if (rootDN != null)
355    {
356      targetDN = rootDN;
357    }
358
359    Entry userEntry;
360    InternalClientConnection conn =
361         new InternalClientConnection(clientConnection.getAuthenticationInfo());
362
363    userEntry = searchUserEntry(conn, operation, targetDN);
364
365    if (userEntry == null)
366    {
367      return;
368    }
369    // Get the password policy state for the user entry.
370    PasswordPolicyState pwpState;
371    try
372    {
373      AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
374          false);
375      if (!policy.isPasswordPolicy())
376      {
377        operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
378        operation.appendErrorMessage(ERR_EXTOP_PWPSTATE_ACCOUNT_NOT_LOCAL.get(userEntry));
379        return;
380      }
381      pwpState = (PasswordPolicyState) policy
382          .createAuthenticationPolicyState(userEntry);
383    }
384    catch (DirectoryException de)
385    {
386      logger.traceException(de);
387
388      operation.setResponseData(de);
389      return;
390    }
391
392    PasswordPolicy policy = pwpState.getAuthenticationPolicy();
393    isAccountSetDisabled = false;
394    isAccountSetEnabled = false;
395    // Create a hash set that will be used to hold the types of the return
396    // types that should be included in the response.
397    boolean returnAll;
398    LinkedHashSet<Integer> returnTypes = new LinkedHashSet<>();
399    try
400    {
401      if (!reader.hasNextElement())
402      {
403        // There is no operations sequence.
404        returnAll = true;
405      }
406      else if(reader.peekLength() <= 0)
407      {
408        // There is an operations sequence but its empty.
409        returnAll = true;
410        reader.readStartSequence();
411        reader.readEndSequence();
412      }
413      else
414      {
415        returnAll = false;
416        reader.readStartSequence();
417        while(reader.hasNextElement())
418        {
419          int opType;
420          ArrayList<String> opValues;
421
422          reader.readStartSequence();
423          opType = (int)reader.readInteger();
424
425          if (!reader.hasNextElement())
426          {
427            // There is no values sequence
428            opValues = null;
429          }
430          else if(reader.peekLength() <= 0)
431          {
432            // There is a values sequence but its empty
433            opValues = null;
434            reader.readStartSequence();
435            reader.readEndSequence();
436          }
437          else
438          {
439            reader.readStartSequence();
440            opValues = new ArrayList<>();
441            while (reader.hasNextElement())
442            {
443              opValues.add(reader.readOctetStringAsString());
444            }
445            reader.readEndSequence();
446          }
447          reader.readEndSequence();
448
449          if(!processOp(opType, opValues, operation,
450              returnTypes, pwpState, policy))
451          {
452            return;
453          }
454        }
455        reader.readEndSequence();
456      }
457      reader.readEndSequence();
458
459
460      // If there are any modifications that need to be made to the password
461      // policy state, then apply them now.
462      List<Modification> stateMods = pwpState.getModifications();
463      if (stateMods != null && !stateMods.isEmpty())
464      {
465        ModifyOperation modifyOperation =
466            conn.processModify(targetDN, stateMods);
467        if (modifyOperation.getResultCode() != ResultCode.SUCCESS)
468        {
469          operation.setResultCode(modifyOperation.getResultCode());
470          operation.setErrorMessage(modifyOperation.getErrorMessage());
471          operation.setMatchedDN(modifyOperation.getMatchedDN());
472          operation.setReferralURLs(modifyOperation.getReferralURLs());
473          return;
474        }
475        // Retrieve the updated entry
476        userEntry = searchUserEntry(conn, operation, targetDN);
477        if (userEntry == null)
478        {
479          return;
480        }
481        // And it's updated password policy state
482        try
483        {
484          // We should not need to re-fetch the password policy.
485          pwpState = (PasswordPolicyState) policy
486              .createAuthenticationPolicyState(userEntry);
487        }
488        catch (DirectoryException de)
489        {
490          logger.traceException(de);
491
492          operation.setResponseData(de);
493          return;
494        }
495      }
496    }
497    catch (Exception e)
498    {
499      logger.traceException(e);
500
501      LocalizableMessage message = ERR_PWPSTATE_EXTOP_INVALID_OP_ENCODING.get(
502          e.getLocalizedMessage());
503      operation.appendErrorMessage(message);
504      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
505      return;
506    }
507
508    try
509    {
510      // Construct the sequence of values to return.
511      ByteString responseValue =
512          encodeResponse(dnString, returnAll, returnTypes, pwpState, policy);
513      operation.setResponseOID(OID_PASSWORD_POLICY_STATE_EXTOP);
514      operation.setResponseValue(responseValue);
515      operation.setResultCode(ResultCode.SUCCESS);
516    }
517    catch(Exception e)
518    {
519      // TODO: Need a better message
520      LocalizableMessage message = ERR_PWPSTATE_EXTOP_INVALID_OP_ENCODING.get(
521          e.getLocalizedMessage());
522      operation.appendErrorMessage(message);
523      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
524    }
525    // Post AccountStatus Notifications if needed.
526    if (isAccountSetDisabled)
527    {
528      pwpState.generateAccountStatusNotification(
529            AccountStatusNotificationType.ACCOUNT_DISABLED,
530            userEntry, INFO_MODIFY_ACCOUNT_DISABLED.get(),
531            AccountStatusNotification.createProperties(pwpState, false, -1,
532                 null, null));
533
534    }
535    if (isAccountSetEnabled)
536    {
537      pwpState.generateAccountStatusNotification(
538            AccountStatusNotificationType.ACCOUNT_ENABLED,
539            userEntry, INFO_MODIFY_ACCOUNT_ENABLED.get(),
540            AccountStatusNotification.createProperties(pwpState, false, -1,
541                 null, null));
542    }
543  }
544
545  /**
546   * Searches and returns the entry referenced by targetDN. If there's not
547   * exactly one entry found, an error is reported for the operation.
548   *
549   * @param conn      The internal connection used to issue the search
550   * @param operation The extended operation being processed
551   * @param targetDN  The DN targeted by this operation
552   *
553   * @return the Entry if one and only one is found, null otherwise
554   */
555  private Entry searchUserEntry (InternalClientConnection conn,
556                              ExtendedOperation operation,
557                              DN targetDN)
558  {
559    final SearchRequest request = newSearchRequest(targetDN, SearchScope.BASE_OBJECT, userFilter)
560        .setSizeLimit(1)
561        .addAttribute(requestAttributes);
562    InternalSearchOperation internalSearch = conn.processSearch(request);
563    if (internalSearch.getResultCode() != ResultCode.SUCCESS)
564    {
565      operation.setResultCode(internalSearch.getResultCode());
566      operation.setErrorMessage(internalSearch.getErrorMessage());
567      operation.setMatchedDN(internalSearch.getMatchedDN());
568      operation.setReferralURLs(internalSearch.getReferralURLs());
569      return null;
570    }
571
572    List<SearchResultEntry> matchingEntries = internalSearch.getSearchEntries();
573    if (matchingEntries.isEmpty())
574    {
575      operation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
576      return null;
577    }
578    else if (matchingEntries.size() > 1)
579    {
580      operation.appendErrorMessage(ERR_PWPSTATE_EXTOP_MULTIPLE_ENTRIES.get(targetDN));
581      operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
582      return null;
583    }
584    else
585    {
586      return matchingEntries.get(0);
587    }
588  }
589
590  /**
591   * Encodes the provided information in a form suitable for including in the
592   * response value.
593   *
594   * @param  writer  The ASN1Writer to use to encode.
595   * @param  opType  The operation type to use for the value.
596   * @param  value   The single value to include in the response.
597   *
598   * @throws IOException if an error occurs while encoding.
599   */
600  public static void encode(ASN1Writer writer, int opType, String value)
601      throws IOException
602  {
603    writer.writeStartSequence();
604    writer.writeEnumerated(opType);
605
606    if (value != null)
607    {
608      writer.writeStartSequence();
609      writer.writeOctetString(value);
610      writer.writeEndSequence();
611    }
612
613    writer.writeEndSequence();
614  }
615
616
617
618  /**
619   * Encodes the provided information in a form suitable for including in the
620   * response value.
621   *
622   * @param  writer  The ASN1Writer to use to encode.
623   * @param  opType  The operation type to use for the value.
624   * @param  values  The set of string values to include in the response.
625   *
626   * @throws IOException if an error occurs while encoding.
627   */
628  public static void encode(ASN1Writer writer, int opType, String[] values)
629      throws IOException
630  {
631    writer.writeStartSequence();
632    writer.writeEnumerated(opType);
633
634    if (values != null && values.length > 0)
635    {
636      writer.writeStartSequence();
637      for (String value : values)
638      {
639        writer.writeOctetString(value);
640      }
641      writer.writeEndSequence();
642    }
643
644    writer.writeEndSequence();
645  }
646
647  /**
648   * Encodes the provided information in a form suitable for including in the
649   * response value.
650   *
651   * @param  writer  The ASN1Writer to use to encode.
652   * @param  opType  The operation type to use for the value.
653   * @param  values  The set of timestamp values to include in the response.
654   *
655   * @throws IOException if an error occurs while encoding.
656   */
657  public static void encode(ASN1Writer writer, int opType, List<Long> values)
658      throws IOException
659  {
660    writer.writeStartSequence();
661    writer.writeEnumerated(opType);
662
663    if (values != null && !values.isEmpty())
664    {
665      writer.writeStartSequence();
666      for (long l : values)
667      {
668        writer.writeOctetString(GeneralizedTimeSyntax.format(l));
669      }
670      writer.writeEndSequence();
671    }
672
673    writer.writeEndSequence();
674  }
675
676  private ByteString encodeResponse(ByteString dnString, boolean returnAll,
677                                    LinkedHashSet<Integer> returnTypes,
678                                    PasswordPolicyState pwpState,
679                                    PasswordPolicy policy)
680      throws IOException
681  {
682    ByteStringBuilder builder = new ByteStringBuilder();
683    ASN1Writer writer = ASN1.getWriter(builder);
684    writer.writeStartSequence();
685    writer.writeOctetString(dnString);
686
687    writer.writeStartSequence();
688    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_POLICY_DN))
689    {
690      encode(writer, OP_GET_PASSWORD_POLICY_DN,
691                            policy.getDN().toString());
692    }
693
694    if (returnAll || returnTypes.contains(OP_GET_ACCOUNT_DISABLED_STATE))
695    {
696      encode(writer, OP_GET_ACCOUNT_DISABLED_STATE,
697                            String.valueOf(pwpState.isDisabled()));
698    }
699
700    if (returnAll || returnTypes.contains(OP_GET_ACCOUNT_EXPIRATION_TIME))
701    {
702      String expTimeStr;
703      long expTime = pwpState.getAccountExpirationTime();
704      if (expTime < 0)
705      {
706        expTimeStr = null;
707      }
708      else
709      {
710        expTimeStr = GeneralizedTimeSyntax.format(expTime);
711      }
712
713      encode(writer, OP_GET_ACCOUNT_EXPIRATION_TIME, expTimeStr);
714    }
715
716    if (returnAll ||
717        returnTypes.contains(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION))
718    {
719      String secondsStr = null;
720      long expTime = pwpState.getAccountExpirationTime();
721      if (expTime >= 0)
722      {
723        long seconds = (expTime - pwpState.getCurrentTime()) / 1000;
724        if (seconds > 0)
725        {
726          secondsStr = String.valueOf(seconds);
727        }
728      }
729
730      encode(writer, OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION,
731                            secondsStr);
732    }
733
734    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_CHANGED_TIME))
735    {
736      String timeStr;
737      long changedTime = pwpState.getPasswordChangedTime();
738      if (changedTime < 0)
739      {
740        timeStr = null;
741      }
742      else
743      {
744        timeStr = GeneralizedTimeSyntax.format(changedTime);
745      }
746
747      encode(writer, OP_GET_PASSWORD_CHANGED_TIME, timeStr);
748    }
749
750    if (returnAll ||
751        returnTypes.contains(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME))
752    {
753      String timeStr;
754      long warnedTime = pwpState.getWarnedTime();
755      if (warnedTime < 0)
756      {
757        timeStr = null;
758      }
759      else
760      {
761        timeStr = GeneralizedTimeSyntax.format(warnedTime);
762      }
763
764      encode(writer, OP_GET_PASSWORD_EXPIRATION_WARNED_TIME, timeStr);
765    }
766
767    if (returnAll ||
768        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION))
769    {
770      String secondsStr;
771      int secondsUntilExp = pwpState.getSecondsUntilExpiration();
772      if (secondsUntilExp < 0)
773      {
774        secondsStr = null;
775      }
776      else
777      {
778        secondsStr = String.valueOf(secondsUntilExp);
779      }
780
781      encode(writer, OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION,
782                            secondsStr);
783    }
784
785    if (returnAll ||
786        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING))
787    {
788      String secondsStr;
789      long secondsUntilExp = pwpState.getSecondsUntilExpiration();
790      if (secondsUntilExp < 0)
791      {
792        secondsStr = null;
793      }
794      else
795      {
796        long secondsUntilWarning = secondsUntilExp
797            - policy.getPasswordExpirationWarningInterval();
798        if (secondsUntilWarning <= 0)
799        {
800          secondsStr = "0";
801        }
802        else
803        {
804          secondsStr = String.valueOf(secondsUntilWarning);
805        }
806      }
807
808      encode(writer, OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING,
809                            secondsStr);
810    }
811
812    if (returnAll || returnTypes.contains(OP_GET_AUTHENTICATION_FAILURE_TIMES))
813    {
814      encode(writer, OP_GET_AUTHENTICATION_FAILURE_TIMES,
815                            pwpState.getAuthFailureTimes());
816    }
817
818    if (returnAll || returnTypes.contains(
819                          OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK))
820    {
821      // We have to check whether the account is locked due to failures before
822      // we can get the length of time until the account is unlocked.
823      String secondsStr;
824      if (pwpState.lockedDueToFailures())
825      {
826        int seconds = pwpState.getSecondsUntilUnlock();
827        if (seconds <= 0)
828        {
829          secondsStr = null;
830        }
831        else
832        {
833          secondsStr = String.valueOf(seconds);
834        }
835      }
836      else
837      {
838        secondsStr = null;
839      }
840
841      encode(writer, OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK,
842                            secondsStr);
843    }
844
845    if (returnAll ||
846        returnTypes.contains(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT))
847    {
848      String remainingFailuresStr;
849      int allowedFailureCount = policy.getLockoutFailureCount();
850      if (allowedFailureCount > 0)
851      {
852        int remainingFailures =
853                 allowedFailureCount - pwpState.getAuthFailureTimes().size();
854        if (remainingFailures < 0)
855        {
856          remainingFailures = 0;
857        }
858
859        remainingFailuresStr = String.valueOf(remainingFailures);
860      }
861      else
862      {
863        remainingFailuresStr = null;
864      }
865
866      encode(writer, OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT,
867                            remainingFailuresStr);
868    }
869
870    if (returnAll || returnTypes.contains(OP_GET_LAST_LOGIN_TIME))
871    {
872      String timeStr;
873      long lastLoginTime = pwpState.getLastLoginTime();
874      if (lastLoginTime < 0)
875      {
876        timeStr = null;
877      }
878      else
879      {
880        timeStr = GeneralizedTimeSyntax.format(lastLoginTime);
881      }
882
883      encode(writer, OP_GET_LAST_LOGIN_TIME, timeStr);
884    }
885
886    if (returnAll || returnTypes.contains(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT))
887    {
888      String secondsStr;
889      long lockoutInterval = policy.getIdleLockoutInterval();
890      if (lockoutInterval > 0)
891      {
892        long lastLoginTime = pwpState.getLastLoginTime();
893        if (lastLoginTime < 0)
894        {
895          secondsStr = "0";
896        }
897        else
898        {
899          long lockoutTime = lastLoginTime + lockoutInterval*1000;
900          long currentTime = pwpState.getCurrentTime();
901          int secondsUntilLockout = (int) ((lockoutTime - currentTime) / 1000L);
902          if (secondsUntilLockout <= 0)
903          {
904            secondsStr = "0";
905          }
906          else
907          {
908            secondsStr = String.valueOf(secondsUntilLockout);
909          }
910        }
911      }
912      else
913      {
914        secondsStr = null;
915      }
916
917      encode(writer, OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT, secondsStr);
918    }
919
920    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_RESET_STATE))
921    {
922      encode(writer, OP_GET_PASSWORD_RESET_STATE,
923                            String.valueOf(pwpState.mustChangePassword()));
924    }
925
926    if (returnAll ||
927        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT))
928    {
929      String secondsStr;
930      if (pwpState.mustChangePassword())
931      {
932        long maxAge = policy.getMaxPasswordResetAge();
933        if (maxAge > 0)
934        {
935          long currentTime = pwpState.getCurrentTime();
936          long changedTime = pwpState.getPasswordChangedTime();
937          int changeAge = (int) ((currentTime - changedTime) / 1000L);
938          long timeToLockout = maxAge - changeAge;
939          if (timeToLockout <= 0)
940          {
941            secondsStr = "0";
942          }
943          else
944          {
945            secondsStr = String.valueOf(timeToLockout);
946          }
947        }
948        else
949        {
950          secondsStr = null;
951        }
952      }
953      else
954      {
955        secondsStr = null;
956      }
957
958      encode(writer, OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
959                            secondsStr);
960    }
961
962    if (returnAll || returnTypes.contains(OP_GET_GRACE_LOGIN_USE_TIMES))
963    {
964      encode(writer, OP_GET_GRACE_LOGIN_USE_TIMES,
965                            pwpState.getGraceLoginTimes());
966    }
967
968    if (returnAll || returnTypes.contains(OP_GET_REMAINING_GRACE_LOGIN_COUNT))
969    {
970      String remainingStr;
971      int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
972      if (remainingGraceLogins <= 0)
973      {
974        remainingStr = "0";
975      }
976      else
977      {
978        remainingStr = String.valueOf(remainingGraceLogins);
979      }
980
981      encode(writer, OP_GET_REMAINING_GRACE_LOGIN_COUNT, remainingStr);
982    }
983
984    if (returnAll ||
985        returnTypes.contains(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME))
986    {
987      String timeStr;
988      long requiredChangeTime = pwpState.getRequiredChangeTime();
989      if (requiredChangeTime < 0)
990      {
991        timeStr = null;
992      }
993      else
994      {
995        timeStr = GeneralizedTimeSyntax.format(requiredChangeTime);
996      }
997
998      encode(writer, OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME, timeStr);
999    }
1000
1001    if (returnAll ||
1002        returnTypes.contains(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME))
1003    {
1004      String secondsStr;
1005      long policyRequiredChangeTime = policy.getRequireChangeByTime();
1006      if (policyRequiredChangeTime > 0)
1007      {
1008        long accountRequiredChangeTime = pwpState.getRequiredChangeTime();
1009        if (accountRequiredChangeTime >= policyRequiredChangeTime)
1010        {
1011          secondsStr = null;
1012        }
1013        else
1014        {
1015          long currentTime = pwpState.getCurrentTime();
1016          if (currentTime >= policyRequiredChangeTime)
1017          {
1018            secondsStr = "0";
1019          }
1020          else
1021          {
1022            secondsStr =
1023                 String.valueOf((policyRequiredChangeTime-currentTime) / 1000);
1024
1025          }
1026        }
1027      }
1028      else
1029      {
1030        secondsStr = null;
1031      }
1032
1033      encode(writer, OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
1034                            secondsStr);
1035    }
1036
1037    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_HISTORY))
1038    {
1039      encode(writer, OP_GET_PASSWORD_HISTORY,
1040                            pwpState.getPasswordHistoryValues());
1041    }
1042    writer.writeEndSequence();
1043
1044    writer.writeEndSequence();
1045
1046    return builder.toByteString();
1047  }
1048
1049  private boolean processOp(int opType, ArrayList<String> opValues,
1050                         ExtendedOperation operation,
1051                         LinkedHashSet<Integer> returnTypes,
1052                         PasswordPolicyState pwpState,
1053                         PasswordPolicy policy)
1054  {
1055    switch (opType)
1056    {
1057      case OP_GET_PASSWORD_POLICY_DN:
1058        returnTypes.add(OP_GET_PASSWORD_POLICY_DN);
1059        break;
1060
1061      case OP_GET_ACCOUNT_DISABLED_STATE:
1062        returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
1063        break;
1064
1065      case OP_SET_ACCOUNT_DISABLED_STATE:
1066        if (opValues == null)
1067        {
1068          operation.appendErrorMessage(
1069              ERR_PWPSTATE_EXTOP_NO_DISABLED_VALUE.get());
1070          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1071          return false;
1072        }
1073        else if (opValues.size() != 1)
1074        {
1075          operation.appendErrorMessage(
1076              ERR_PWPSTATE_EXTOP_BAD_DISABLED_VALUE_COUNT.get());
1077          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1078          return false;
1079        }
1080        else
1081        {
1082          String value = opValues.get(0);
1083          if ("true".equalsIgnoreCase(value))
1084          {
1085            pwpState.setDisabled(true);
1086            isAccountSetDisabled = true;
1087          }
1088          else if ("false".equalsIgnoreCase(value))
1089          {
1090            pwpState.setDisabled(false);
1091            isAccountSetEnabled = true;
1092          }
1093          else
1094          {
1095            operation.appendErrorMessage(
1096                ERR_PWPSTATE_EXTOP_BAD_DISABLED_VALUE.get());
1097            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1098            return false;
1099          }
1100        }
1101
1102        returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
1103        break;
1104
1105      case OP_CLEAR_ACCOUNT_DISABLED_STATE:
1106        pwpState.setDisabled(false);
1107        isAccountSetEnabled = true;
1108        returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
1109        break;
1110
1111      case OP_GET_ACCOUNT_EXPIRATION_TIME:
1112        returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
1113        break;
1114
1115      case OP_SET_ACCOUNT_EXPIRATION_TIME:
1116        if (opValues == null)
1117        {
1118          pwpState.setAccountExpirationTime(pwpState.getCurrentTime());
1119        }
1120        else if (opValues.size() != 1)
1121        {
1122          operation.appendErrorMessage(
1123              ERR_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE_COUNT.get());
1124          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1125          return false;
1126        }
1127        else
1128        {
1129          try
1130          {
1131            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1132            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1133            pwpState.setAccountExpirationTime(time);
1134          }
1135          catch (LocalizedIllegalArgumentException e)
1136          {
1137            operation.appendErrorMessage(
1138                ERR_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE.get(opValues.get(0), e.getMessageObject()));
1139            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1140            return false;
1141          }
1142        }
1143
1144        returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
1145        break;
1146
1147      case OP_CLEAR_ACCOUNT_EXPIRATION_TIME:
1148        pwpState.clearAccountExpirationTime();
1149        returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
1150        break;
1151
1152      case OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION:
1153        returnTypes.add(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION);
1154        break;
1155
1156      case OP_GET_PASSWORD_CHANGED_TIME:
1157        returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
1158        break;
1159
1160      case OP_SET_PASSWORD_CHANGED_TIME:
1161        if (opValues == null)
1162        {
1163          pwpState.setPasswordChangedTime();
1164        }
1165        else if (opValues.size() != 1)
1166        {
1167          operation.appendErrorMessage(
1168              ERR_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE_COUNT.get());
1169          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1170          return false;
1171        }
1172        else
1173        {
1174          try
1175          {
1176            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1177            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1178            pwpState.setPasswordChangedTime(time);
1179          }
1180          catch (LocalizedIllegalArgumentException e)
1181          {
1182            operation.appendErrorMessage(
1183                ERR_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE.get(opValues.get(0), e.getMessageObject()));
1184            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1185            return false;
1186          }
1187        }
1188
1189        returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
1190        break;
1191
1192      case OP_CLEAR_PASSWORD_CHANGED_TIME:
1193        pwpState.clearPasswordChangedTime();
1194        returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
1195        break;
1196
1197      case OP_GET_PASSWORD_EXPIRATION_WARNED_TIME:
1198        returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
1199        break;
1200
1201      case OP_SET_PASSWORD_EXPIRATION_WARNED_TIME:
1202        if (opValues == null)
1203        {
1204          pwpState.setWarnedTime();
1205        }
1206        else if (opValues.size() != 1)
1207        {
1208          operation.appendErrorMessage(
1209              ERR_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE_COUNT.get());
1210          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1211          return false;
1212        }
1213        else
1214        {
1215          try
1216          {
1217            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1218            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1219            pwpState.setWarnedTime(time);
1220          }
1221          catch (LocalizedIllegalArgumentException e)
1222          {
1223            operation.appendErrorMessage(
1224                ERR_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE.get(opValues.get(0), e.getMessageObject()));
1225            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1226            return false;
1227          }
1228        }
1229
1230        returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
1231        break;
1232
1233      case OP_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME:
1234        pwpState.clearWarnedTime();
1235        returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
1236        break;
1237
1238      case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION:
1239        returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION);
1240        break;
1241
1242      case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING:
1243        returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING);
1244        break;
1245
1246      case OP_GET_AUTHENTICATION_FAILURE_TIMES:
1247        returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
1248        break;
1249
1250      case OP_ADD_AUTHENTICATION_FAILURE_TIME:
1251        if (opValues == null)
1252        {
1253          if (policy.getLockoutFailureCount() == 0)
1254          {
1255            returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
1256            break;
1257          }
1258
1259          pwpState.updateAuthFailureTimes();
1260        }
1261        else if (opValues.size() != 1)
1262        {
1263          operation.appendErrorMessage(
1264              ERR_PWPSTATE_EXTOP_BAD_ADD_FAILURE_TIME_COUNT.get());
1265          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1266          return false;
1267        }
1268        else
1269        {
1270          try
1271          {
1272            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1273            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1274            List<Long> authFailureTimes = pwpState.getAuthFailureTimes();
1275            ArrayList<Long> newFailureTimes = new ArrayList<>(authFailureTimes.size()+1);
1276            newFailureTimes.addAll(authFailureTimes);
1277            newFailureTimes.add(time);
1278            pwpState.setAuthFailureTimes(newFailureTimes);
1279          }
1280          catch (LocalizedIllegalArgumentException e)
1281          {
1282            LocalizableMessage message =
1283                ERR_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME.get(opValues.get(0), e.getMessageObject());
1284            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1285            operation.appendErrorMessage(message);
1286            return false;
1287          }
1288        }
1289
1290        returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
1291        break;
1292
1293      case OP_SET_AUTHENTICATION_FAILURE_TIMES:
1294        if (opValues == null)
1295        {
1296          pwpState.setAuthFailureTimes(newArrayList(pwpState.getCurrentTime()));
1297        }
1298        else
1299        {
1300          ArrayList<Long> valueList = new ArrayList<>(opValues.size());
1301          for (String value : opValues)
1302          {
1303            try
1304            {
1305              valueList.add(GeneralizedTime.valueOf(value).getTimeInMillis());
1306            }
1307            catch (LocalizedIllegalArgumentException e)
1308            {
1309              LocalizableMessage message =
1310                  ERR_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME.get(value, e.getMessageObject());
1311              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1312              operation.appendErrorMessage(message);
1313              return false;
1314            }
1315          }
1316          pwpState.setAuthFailureTimes(valueList);
1317        }
1318
1319        returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
1320        break;
1321
1322      case OP_CLEAR_AUTHENTICATION_FAILURE_TIMES:
1323        pwpState.clearFailureLockout();
1324        returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
1325        break;
1326
1327      case OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK:
1328        returnTypes.add(OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK);
1329        break;
1330
1331      case OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT:
1332        returnTypes.add(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT);
1333        break;
1334
1335      case OP_GET_LAST_LOGIN_TIME:
1336        returnTypes.add(OP_GET_LAST_LOGIN_TIME);
1337        break;
1338
1339      case OP_SET_LAST_LOGIN_TIME:
1340        if (opValues == null)
1341        {
1342          pwpState.setLastLoginTime();
1343        }
1344        else if (opValues.size() != 1)
1345        {
1346          operation.appendErrorMessage(
1347              ERR_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME_COUNT.get());
1348          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1349          return false;
1350        }
1351        else
1352        {
1353          try
1354          {
1355            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1356            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1357            pwpState.setLastLoginTime(time);
1358          }
1359          catch (LocalizedIllegalArgumentException e)
1360          {
1361            operation.appendErrorMessage(
1362                ERR_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME.get(opValues.get(0), e.getMessageObject()));
1363            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1364            return false;
1365          }
1366        }
1367
1368        returnTypes.add(OP_GET_LAST_LOGIN_TIME);
1369        break;
1370
1371      case OP_CLEAR_LAST_LOGIN_TIME:
1372        pwpState.clearLastLoginTime();
1373        returnTypes.add(OP_GET_LAST_LOGIN_TIME);
1374        break;
1375
1376      case OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT:
1377        returnTypes.add(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT);
1378        break;
1379
1380      case OP_GET_PASSWORD_RESET_STATE:
1381        returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
1382        break;
1383
1384      case OP_SET_PASSWORD_RESET_STATE:
1385        if (opValues == null)
1386        {
1387          operation.appendErrorMessage(
1388              ERR_PWPSTATE_EXTOP_NO_RESET_STATE_VALUE.get());
1389          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1390          return false;
1391        }
1392        else if (opValues.size() != 1)
1393        {
1394          operation.appendErrorMessage(
1395              ERR_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE_COUNT.get());
1396          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1397          return false;
1398        }
1399        else
1400        {
1401          String value = opValues.get(0);
1402          if ("true".equalsIgnoreCase(value))
1403          {
1404            pwpState.setMustChangePassword(true);
1405          }
1406          else if ("false".equalsIgnoreCase(value))
1407          {
1408            pwpState.setMustChangePassword(false);
1409          }
1410          else
1411          {
1412            operation.appendErrorMessage(
1413                ERR_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE.get());
1414            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1415            return false;
1416          }
1417        }
1418
1419        returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
1420        break;
1421
1422      case OP_CLEAR_PASSWORD_RESET_STATE:
1423        pwpState.setMustChangePassword(false);
1424        returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
1425        break;
1426
1427      case OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT:
1428        returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT);
1429        break;
1430
1431      case OP_GET_GRACE_LOGIN_USE_TIMES:
1432        returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
1433        break;
1434
1435      case OP_ADD_GRACE_LOGIN_USE_TIME:
1436        if (opValues == null)
1437        {
1438          pwpState.updateGraceLoginTimes();
1439        }
1440        else if (opValues.size() != 1)
1441        {
1442          operation.appendErrorMessage(
1443              ERR_PWPSTATE_EXTOP_BAD_ADD_GRACE_LOGIN_TIME_COUNT.get());
1444          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1445          return false;
1446        }
1447        else
1448        {
1449          try
1450          {
1451            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1452            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1453            List<Long> authFailureTimes = pwpState.getGraceLoginTimes();
1454            ArrayList<Long> newGraceTimes = new ArrayList<>(authFailureTimes.size()+1);
1455            newGraceTimes.addAll(authFailureTimes);
1456            newGraceTimes.add(time);
1457            pwpState.setGraceLoginTimes(newGraceTimes);
1458          }
1459          catch (LocalizedIllegalArgumentException e)
1460          {
1461            LocalizableMessage message =
1462                ERR_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME.get(opValues.get(0), e.getMessageObject());
1463            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1464            operation.appendErrorMessage(message);
1465            return false;
1466          }
1467        }
1468
1469        returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
1470        break;
1471
1472      case OP_SET_GRACE_LOGIN_USE_TIMES:
1473        if (opValues == null)
1474        {
1475          pwpState.setGraceLoginTimes(newArrayList(pwpState.getCurrentTime()));
1476        }
1477        else
1478        {
1479          ArrayList<Long> valueList = new ArrayList<>(opValues.size());
1480          for (String s : opValues)
1481          {
1482            try
1483            {
1484              valueList.add(GeneralizedTime.valueOf(s).getTimeInMillis());
1485            }
1486            catch (LocalizedIllegalArgumentException e)
1487            {
1488              LocalizableMessage message = ERR_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME.get(
1489                  s, e.getMessageObject());
1490              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1491              operation.appendErrorMessage(message);
1492              return false;
1493            }
1494          }
1495          pwpState.setGraceLoginTimes(valueList);
1496        }
1497
1498        returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
1499        break;
1500
1501      case OP_CLEAR_GRACE_LOGIN_USE_TIMES:
1502        pwpState.clearGraceLoginTimes();
1503        returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
1504        break;
1505
1506      case OP_GET_REMAINING_GRACE_LOGIN_COUNT:
1507        returnTypes.add(OP_GET_REMAINING_GRACE_LOGIN_COUNT);
1508        break;
1509
1510      case OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME:
1511        returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
1512        break;
1513
1514      case OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME:
1515        if (opValues == null)
1516        {
1517          pwpState.setRequiredChangeTime();
1518        }
1519        else if (opValues.size() != 1)
1520        {
1521          operation.appendErrorMessage(
1522              ERR_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME_COUNT.get());
1523          operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1524          return false;
1525        }
1526        else
1527        {
1528          try
1529          {
1530            ByteString valueString = ByteString.valueOfUtf8(opValues.get(0));
1531            long time = GeneralizedTime.valueOf(valueString.toString()).getTimeInMillis();
1532            pwpState.setRequiredChangeTime(time);
1533          }
1534          catch (LocalizedIllegalArgumentException e)
1535          {
1536            operation.appendErrorMessage(
1537                ERR_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME.get(
1538                    opValues.get(0),
1539                    e.getMessageObject()));
1540            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1541            return false;
1542          }
1543        }
1544
1545        returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
1546        break;
1547
1548      case OP_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME:
1549        pwpState.clearRequiredChangeTime();
1550        returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
1551        break;
1552
1553      case OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME:
1554        returnTypes.add(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME);
1555        break;
1556
1557      case OP_GET_PASSWORD_HISTORY:
1558        returnTypes.add(OP_GET_PASSWORD_HISTORY);
1559        break;
1560
1561      case OP_CLEAR_PASSWORD_HISTORY:
1562        pwpState.clearPasswordHistory();
1563        returnTypes.add(OP_GET_PASSWORD_HISTORY);
1564        break;
1565
1566      default:
1567        operation.appendErrorMessage(ERR_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE.get(opType));
1568        operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
1569        return false;
1570    }
1571
1572    return true;
1573  }
1574
1575  /** {@inheritDoc} */
1576  @Override
1577  public String getExtendedOperationOID()
1578  {
1579    return OID_PASSWORD_POLICY_STATE_EXTOP;
1580  }
1581
1582  /** {@inheritDoc} */
1583  @Override
1584  public String getExtendedOperationName()
1585  {
1586    return "Password Policy State";
1587  }
1588}