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