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