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 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.workflowelement.localbackend;
018
019import java.util.List;
020
021import org.forgerock.i18n.LocalizableMessage;
022import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
023import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025import org.forgerock.opendj.ldap.ByteString;
026import org.forgerock.opendj.ldap.DN;
027import org.forgerock.opendj.ldap.ResultCode;
028import org.forgerock.opendj.ldap.schema.AttributeType;
029import org.forgerock.opendj.server.config.meta.PasswordPolicyCfgDefn;
030import org.opends.server.api.AuthenticationPolicyState;
031import org.opends.server.api.Backend;
032import org.opends.server.api.ClientConnection;
033import org.opends.server.api.SASLMechanismHandler;
034import org.opends.server.controls.*;
035import org.opends.server.core.*;
036import org.opends.server.types.*;
037import org.opends.server.types.operation.PostOperationBindOperation;
038import org.opends.server.types.operation.PostResponseBindOperation;
039import org.opends.server.types.operation.PreOperationBindOperation;
040
041import static org.opends.messages.CoreMessages.*;
042import static org.opends.server.config.ConfigConstants.*;
043import static org.opends.server.types.AbstractOperation.*;
044import static org.opends.server.types.Privilege.*;
045import static org.opends.server.util.ServerConstants.*;
046import static org.opends.server.util.StaticUtils.*;
047
048/**
049 * This class defines an operation used to bind against the Directory Server,
050 * with the bound user entry within a local backend.
051 */
052public class LocalBackendBindOperation
053       extends BindOperationWrapper
054       implements PreOperationBindOperation, PostOperationBindOperation,
055                  PostResponseBindOperation
056{
057  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
058
059  /** The backend in which the bind operation should be processed. */
060  private Backend<?> backend;
061
062  /**
063   * Indicates whether the bind response should include the first warning
064   * for an upcoming password expiration.
065   */
066  private boolean isFirstWarning;
067  /** Indicates whether this bind is using a grace login for the user. */
068  private boolean isGraceLogin;
069
070  /** Indicates whether the user must change his/her password before doing anything else. */
071  private boolean mustChangePassword;
072
073  /** Indicates whether the user requested the password policy control. */
074  private boolean pwPolicyControlRequested;
075
076  /**
077   * Indicates whether the server should return the authorization ID as a
078   * control in the bind response.
079   */
080  private boolean returnAuthzID;
081
082  /** Indicates whether to execute post-operation plugins. */
083  private boolean executePostOpPlugins;
084
085  /** The client connection associated with this bind operation. */
086  private ClientConnection clientConnection;
087
088  /** The bind DN provided by the client. */
089  private DN bindDN;
090
091  /** The value to use for the password policy warning. */
092  private int pwPolicyWarningValue;
093  /** The lookthrough limit that should be enforced for the user. */
094  private int lookthroughLimit;
095  /** The size limit that should be enforced for the user. */
096  private int sizeLimit;
097  /** The time limit that should be enforced for the user. */
098  private int timeLimit;
099  /** The idle time limit that should be enforced for the user. */
100  private long idleTimeLimit;
101
102  /** Authentication policy state. */
103  private AuthenticationPolicyState authPolicyState;
104
105  /** The password policy error type for this bind operation. */
106  private PasswordPolicyErrorType pwPolicyErrorType;
107  /** The password policy warning type for this bind operation. */
108  private PasswordPolicyWarningType pwPolicyWarningType;
109
110  /** The plugin config manager for the Directory Server. */
111  private PluginConfigManager pluginConfigManager;
112
113  /** The SASL mechanism used for this bind operation. */
114  private String saslMechanism;
115
116  /**
117   * Creates a new operation that may be used to bind where
118   * the bound user entry is stored in a local backend of the Directory Server.
119   *
120   * @param bind The operation to enhance.
121   */
122  LocalBackendBindOperation(BindOperation bind)
123  {
124    super(bind);
125    LocalBackendWorkflowElement.attachLocalOperation (bind, this);
126  }
127
128  /**
129   * Process this bind operation in a local backend.
130   *
131   * @param wfe
132   *          The local backend work-flow element.
133   */
134  public void processLocalBind(LocalBackendWorkflowElement wfe)
135  {
136    this.backend = wfe.getBackend();
137
138    // Initialize a number of variables for use during the bind processing.
139    clientConnection         = getClientConnection();
140    returnAuthzID            = false;
141    executePostOpPlugins     = false;
142    sizeLimit                = DirectoryServer.getSizeLimit();
143    timeLimit                = DirectoryServer.getTimeLimit();
144    lookthroughLimit         = DirectoryServer.getLookthroughLimit();
145    idleTimeLimit            = DirectoryServer.getIdleTimeLimit();
146    bindDN                   = getBindDN();
147    saslMechanism            = getSASLMechanism();
148    authPolicyState          = null;
149    pwPolicyErrorType        = null;
150    pwPolicyControlRequested = false;
151    isGraceLogin             = false;
152    isFirstWarning           = false;
153    mustChangePassword       = false;
154    pwPolicyWarningType      = null;
155    pwPolicyWarningValue     = -1 ;
156    pluginConfigManager      = DirectoryServer.getPluginConfigManager();
157
158    processBind();
159
160    // Update the user's account with any password policy changes that may be
161    // required.
162    try
163    {
164      if (authPolicyState != null)
165      {
166        authPolicyState.finalizeStateAfterBind();
167      }
168    }
169    catch (DirectoryException de)
170    {
171      logger.traceException(de);
172
173      setResponseData(de);
174    }
175
176    // Invoke the post-operation bind plugins.
177    if (executePostOpPlugins)
178    {
179      processOperationResult(this, pluginConfigManager.invokePostOperationBindPlugins(this));
180    }
181
182    // Update the authentication information for the user.
183    AuthenticationInfo authInfo = getAuthenticationInfo();
184    if (getResultCode() == ResultCode.SUCCESS && authInfo != null)
185    {
186      clientConnection.setAuthenticationInfo(authInfo);
187      clientConnection.setSizeLimit(sizeLimit);
188      clientConnection.setTimeLimit(timeLimit);
189      clientConnection.setIdleTimeLimit(idleTimeLimit);
190      clientConnection.setLookthroughLimit(lookthroughLimit);
191      clientConnection.setMustChangePassword(mustChangePassword);
192
193      if (returnAuthzID)
194      {
195        addResponseControl(new AuthorizationIdentityResponseControl(
196                                    authInfo.getAuthorizationDN()));
197      }
198    }
199
200    // See if we need to send a password policy control to the client.  If so,
201    // then add it to the response.
202    if (pwPolicyControlRequested)
203    {
204      addResponseControl(new PasswordPolicyResponseControl(
205          pwPolicyWarningType, pwPolicyWarningValue, pwPolicyErrorType));
206    }
207    else
208    {
209      if (getResultCode() == ResultCode.SUCCESS)
210      {
211        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
212        {
213          addResponseControl(new PasswordExpiredControl());
214        }
215        else if (pwPolicyWarningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
216        {
217          addResponseControl(new PasswordExpiringControl(pwPolicyWarningValue));
218        }
219        else if (mustChangePassword)
220        {
221          addResponseControl(new PasswordExpiredControl());
222        }
223      }
224      else
225      {
226        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
227        {
228          addResponseControl(new PasswordExpiredControl());
229        }
230      }
231    }
232  }
233
234  /**
235   * Performs the checks and processing necessary for the current bind operation
236   * (simple or SASL).
237   */
238  private void processBind()
239  {
240    // Check to see if the client has permission to perform the bind.
241
242    // FIXME: for now assume that this will check all permission
243    // pertinent to the operation. This includes any controls specified.
244    try
245    {
246      if (!AccessControlConfigManager.getInstance().getAccessControlHandler().isAllowed(this))
247      {
248        setResultCode(ResultCode.INVALID_CREDENTIALS);
249        setAuthFailureReason(ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get());
250        return;
251      }
252    }
253    catch (DirectoryException e)
254    {
255      setResultCode(e.getResultCode());
256      setAuthFailureReason(e.getMessageObject());
257      return;
258    }
259
260    // Check to see if there are any controls in the request. If so, then see
261    // if there is any special processing required.
262    try
263    {
264      handleRequestControls();
265    }
266    catch (DirectoryException de)
267    {
268      logger.traceException(de);
269
270      setResponseData(de);
271      return;
272    }
273
274    // Check to see if this is a simple bind or a SASL bind and process
275    // accordingly.
276    try
277    {
278      switch (getAuthenticationType())
279      {
280      case SIMPLE:
281        processSimpleBind();
282        break;
283
284      case SASL:
285        processSASLBind();
286        break;
287
288      default:
289        // Send a protocol error response to the client and disconnect.
290        // We should never come here.
291        setResultCode(ResultCode.PROTOCOL_ERROR);
292      }
293    }
294    catch (DirectoryException de)
295    {
296      logger.traceException(de);
297
298      if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS)
299      {
300        setResultCode(ResultCode.INVALID_CREDENTIALS);
301        setAuthFailureReason(de.getMessageObject());
302      }
303      else
304      {
305        setResponseData(de);
306      }
307    }
308  }
309
310  /**
311   * Handles request control processing for this bind operation.
312   *
313   * @throws  DirectoryException  If there is a problem with any of the
314   *                              controls.
315   */
316  private void handleRequestControls() throws DirectoryException
317  {
318    LocalBackendWorkflowElement.removeAllDisallowedControls(bindDN, this);
319
320    for (Control c : getRequestControls())
321    {
322      final String oid = c.getOID();
323
324      if (OID_AUTHZID_REQUEST.equals(oid))
325      {
326        returnAuthzID = true;
327      }
328      else if (OID_PASSWORD_POLICY_CONTROL.equals(oid))
329      {
330        pwPolicyControlRequested = true;
331      }
332      else if (c.isCritical())
333      {
334        throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
335            ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
336      }
337    }
338  }
339
340  /**
341   * Performs the processing necessary for a simple bind operation.
342   *
343   * @return  {@code true} if processing should continue for the operation, or
344   *          {@code false} if not.
345   *
346   * @throws  DirectoryException  If a problem occurs that should cause the bind
347   *                              operation to fail.
348   */
349  private boolean processSimpleBind() throws DirectoryException
350  {
351    // See if this is an anonymous bind. If so, then determine whether to allow it.
352    ByteString simplePassword = getSimplePassword();
353    if (simplePassword == null || simplePassword.length() == 0)
354    {
355      return processAnonymousSimpleBind();
356    }
357
358    // See if the bind DN is actually one of the alternate root DNs
359    // defined in the server.  If so, then replace it with the actual DN
360    // for that user.
361    DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
362    if (actualRootDN != null)
363    {
364      bindDN = actualRootDN;
365    }
366
367    Entry userEntry;
368    try
369    {
370      userEntry = backend.getEntry(bindDN);
371    }
372    catch (DirectoryException de)
373    {
374      logger.traceException(de);
375
376      userEntry = null;
377
378      if (de.getResultCode() == ResultCode.REFERRAL)
379      {
380        // Re-throw referral exceptions - these should be passed back to the client.
381        throw de;
382      }
383      else
384      {
385        // Replace other exceptions in case they expose any sensitive information.
386        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, de.getMessageObject());
387      }
388    }
389
390    if (userEntry == null)
391    {
392      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
393                                   ERR_BIND_OPERATION_UNKNOWN_USER.get());
394    }
395    setUserEntryDN(userEntry.getName());
396
397    // Check to see if the user has a password. If not, then fail.
398    // FIXME -- We need to have a way to enable/disable debugging.
399    authPolicyState = AuthenticationPolicyState.forUser(userEntry, false);
400    if (authPolicyState.isPasswordPolicy())
401    {
402      // Account is managed locally.
403      PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
404      PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
405
406      AttributeType pwType = policy.getPasswordAttribute();
407      if (userEntry.getAttribute(pwType).isEmpty())
408      {
409        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
410            ERR_BIND_OPERATION_NO_PASSWORD.get());
411      }
412
413      // Perform a number of password policy state checks for the
414      // non-authenticated user.
415      checkUnverifiedPasswordPolicyState(userEntry, null);
416
417      // Invoke pre-operation plugins.
418      if (!invokePreOpPlugins())
419      {
420        return false;
421      }
422
423      // Determine whether the provided password matches any of the stored
424      // passwords for the user.
425      if (pwPolicyState.passwordMatches(simplePassword))
426      {
427        setResultCode(ResultCode.SUCCESS);
428
429        checkVerifiedPasswordPolicyState(userEntry, null);
430
431        if (DirectoryServer.lockdownMode()
432            && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN))
433        {
434          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
435              ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
436        }
437        setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
438            DirectoryServer.isRootDN(userEntry.getName())));
439
440        // Set resource limits for the authenticated user.
441        setResourceLimits(userEntry);
442
443        // Perform any remaining processing for a successful simple
444        // authentication.
445        pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
446        pwPolicyState.clearFailureLockout();
447
448        if (isFirstWarning)
449        {
450          pwPolicyState.setWarnedTime();
451
452          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
453          LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING
454              .get(secondsToTimeString(numSeconds));
455
456          pwPolicyState.generateAccountStatusNotification(
457              AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
458              AccountStatusNotification.createProperties(pwPolicyState,
459                  false, numSeconds, null, null));
460        }
461
462        if (isGraceLogin)
463        {
464          pwPolicyState.updateGraceLoginTimes();
465        }
466
467        pwPolicyState.setLastLoginTime();
468      }
469      else
470      {
471        setResultCode(ResultCode.INVALID_CREDENTIALS);
472        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
473
474        if (policy.getLockoutFailureCount() > 0)
475        {
476          updateFailureCount(userEntry, pwPolicyState);
477        }
478      }
479    }
480    else
481    {
482      // Check to see if the user is administratively disabled or locked.
483      if (authPolicyState.isDisabled())
484      {
485        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
486            ERR_BIND_OPERATION_ACCOUNT_DISABLED.get());
487      }
488
489      // Invoke pre-operation plugins.
490      if (!invokePreOpPlugins())
491      {
492        return false;
493      }
494
495      if (authPolicyState.passwordMatches(simplePassword))
496      {
497        setResultCode(ResultCode.SUCCESS);
498
499        if (DirectoryServer.lockdownMode()
500            && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN))
501        {
502          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
503              ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
504        }
505        setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
506            DirectoryServer.isRootDN(userEntry.getName())));
507
508        // Set resource limits for the authenticated user.
509        setResourceLimits(userEntry);
510      }
511      else
512      {
513        setResultCode(ResultCode.INVALID_CREDENTIALS);
514        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
515      }
516    }
517
518    return true;
519  }
520
521  /**
522   * Performs the processing necessary for an anonymous simple bind.
523   *
524   * @return  {@code true} if processing should continue for the operation, or
525   *          {@code false} if not.
526   * @throws  DirectoryException  If a problem occurs that should cause the bind
527   *                              operation to fail.
528   */
529  private boolean processAnonymousSimpleBind() throws DirectoryException
530  {
531    // If the server is in lockdown mode, then fail.
532    if (DirectoryServer.lockdownMode())
533    {
534      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
535                                   ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
536    }
537
538    // If there is a bind DN, then see whether that is acceptable.
539    if (DirectoryServer.bindWithDNRequiresPassword()
540        && bindDN != null && !bindDN.isRootDN())
541    {
542      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
543                                   ERR_BIND_DN_BUT_NO_PASSWORD.get());
544    }
545
546    // Invoke pre-operation plugins.
547    if (!invokePreOpPlugins())
548    {
549      return false;
550    }
551
552    setResultCode(ResultCode.SUCCESS);
553    setAuthenticationInfo(new AuthenticationInfo());
554    return true;
555  }
556
557  /**
558   * Performs the processing necessary for a SASL bind operation.
559   *
560   * @return  {@code true} if processing should continue for the operation, or
561   *          {@code false} if not.
562   *
563   * @throws  DirectoryException  If a problem occurs that should cause the bind
564   *                              operation to fail.
565   */
566  private boolean processSASLBind() throws DirectoryException
567  {
568    // Get the appropriate authentication handler for this request based
569    // on the SASL mechanism.  If there is none, then fail.
570    SASLMechanismHandler<?> saslHandler =
571         DirectoryServer.getSASLMechanismHandler(saslMechanism);
572    if (saslHandler == null)
573    {
574      throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED,
575                     ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(
576                          saslMechanism));
577    }
578
579    // Check to see if the client has sufficient permission to perform the bind.
580    // NYI
581
582    // Invoke pre-operation plugins.
583    if (!invokePreOpPlugins())
584    {
585      return false;
586    }
587
588    // Actually process the SASL bind.
589    saslHandler.processSASLBind(this);
590
591    // If the server is operating in lockdown mode, then we will need to
592    // ensure that the authentication was successful and performed as a
593    // root user to continue.
594    Entry saslAuthUserEntry = getSASLAuthUserEntry();
595    if (DirectoryServer.lockdownMode())
596    {
597      ResultCode resultCode = getResultCode();
598      if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS
599          && (resultCode != ResultCode.SUCCESS
600              || saslAuthUserEntry == null
601              || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN)))
602      {
603        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
604                                     ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
605      }
606    }
607
608    // Create the password policy state object.
609    if (saslAuthUserEntry != null)
610    {
611      setUserEntryDN(saslAuthUserEntry.getName());
612
613      // FIXME -- Need to have a way to enable debugging.
614      authPolicyState = AuthenticationPolicyState.forUser(
615          saslAuthUserEntry, false);
616      if (authPolicyState.isPasswordPolicy())
617      {
618        // Account is managed locally: perform password policy checks that can
619        // be completed before we have checked authentication was successful.
620        checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);
621      }
622    }
623
624    // Determine whether the authentication was successful and perform
625    // any remaining password policy processing accordingly.
626    ResultCode resultCode = getResultCode();
627    if (resultCode == ResultCode.SUCCESS)
628    {
629      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
630      {
631        checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);
632
633        PasswordPolicyState pwPolicyState =
634          (PasswordPolicyState) authPolicyState;
635
636        if (saslHandler.isPasswordBased(saslMechanism) &&
637            pwPolicyState.mustChangePassword())
638        {
639          mustChangePassword = true;
640        }
641
642        if (isFirstWarning)
643        {
644          pwPolicyState.setWarnedTime();
645
646          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
647          LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(
648                                 secondsToTimeString(numSeconds));
649
650          pwPolicyState.generateAccountStatusNotification(
651               AccountStatusNotificationType.PASSWORD_EXPIRING,
652               saslAuthUserEntry, m,
653               AccountStatusNotification.createProperties(pwPolicyState,
654                     false, numSeconds, null, null));
655        }
656
657        if (isGraceLogin)
658        {
659          pwPolicyState.updateGraceLoginTimes();
660        }
661
662        pwPolicyState.setLastLoginTime();
663      }
664
665      // Set appropriate resource limits for the user (note that SASL ANONYMOUS
666      // does not have a user).
667      if (saslAuthUserEntry != null)
668      {
669        setResourceLimits(saslAuthUserEntry);
670      }
671    }
672    else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
673    {
674      // FIXME -- Is any special processing needed here?
675      return false;
676    }
677    else
678    {
679      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
680      {
681        PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
682
683        if (saslHandler.isPasswordBased(saslMechanism)
684            && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0)
685        {
686          updateFailureCount(saslAuthUserEntry, pwPolicyState);
687        }
688      }
689    }
690
691    return true;
692  }
693
694  private void updateFailureCount(Entry userEntry, PasswordPolicyState pwPolicyState)
695  {
696    if (pwPolicyState.lockedDueToFailures())
697    {
698      // Account is already locked, nothing to do
699      return;
700    }
701    pwPolicyState.updateAuthFailureTimes();
702    if (pwPolicyState.lockedDueToFailures())
703    {
704      AccountStatusNotificationType notificationType;
705      boolean tempLocked;
706      LocalizableMessage m;
707
708      int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
709      if (lockoutDuration > -1)
710      {
711        notificationType = AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
712        tempLocked = true;
713        m =
714            ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED
715                .get(secondsToTimeString(lockoutDuration));
716      }
717      else
718      {
719        notificationType = AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
720        tempLocked = false;
721        m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
722      }
723
724      pwPolicyState.generateAccountStatusNotification(notificationType,
725          userEntry, m, AccountStatusNotification.createProperties(
726              pwPolicyState, tempLocked, -1, null, null));
727    }
728  }
729
730  private boolean invokePreOpPlugins()
731  {
732    executePostOpPlugins = true;
733    return processOperationResult(this, pluginConfigManager.invokePreOperationBindPlugins(this));
734  }
735
736  /**
737   * Validates a number of password policy state constraints for the user. This
738   * will be called before the offered credentials are checked.
739   *
740   * @param userEntry
741   *          The entry for the user that is authenticating.
742   * @param saslHandler
743   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
744   *          for a simple bind.
745   * @throws DirectoryException
746   *           If a problem occurs that should cause the bind to fail.
747   */
748  private void checkUnverifiedPasswordPolicyState(
749      Entry userEntry, SASLMechanismHandler<?> saslHandler)
750      throws DirectoryException
751  {
752    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
753    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
754
755
756    // If the password policy is configured to track authentication failures or
757    // keep the last login time and the associated backend is disabled, then we
758    // may need to reject the bind immediately.
759    if ((policy.getStateUpdateFailurePolicy() ==
760         PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
761        ((policy.getLockoutFailureCount() > 0) ||
762         ((policy.getLastLoginTimeAttribute() != null) &&
763          (policy.getLastLoginTimeFormat() != null))) &&
764        ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) ||
765         (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
766    {
767      // This policy isn't applicable to root users, so if it's a root
768      // user then ignore it.
769      if (! DirectoryServer.isRootDN(userEntry.getName()))
770      {
771        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
772            ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName()));
773      }
774    }
775
776    // Check to see if the authentication must be done in a secure
777    // manner.  If so, then the client connection must be secure.
778    if (policy.isRequireSecureAuthentication()
779        && !clientConnection.isSecure())
780    {
781      boolean isSASLBind = saslHandler != null;
782      if (isSASLBind)
783      {
784        if (! saslHandler.isSecure(saslMechanism))
785        {
786          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
787              ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName()));
788        }
789      }
790      else
791      {
792        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
793                       ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get());
794      }
795    }
796  }
797
798  /**
799   * Perform policy checks for accounts when the credentials are correct.
800   *
801   * @param userEntry
802   *          The entry for the user that is authenticating.
803   * @param saslHandler
804   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
805   *          for a simple bind.
806   * @throws DirectoryException
807   *           If a problem occurs that should cause the bind to fail.
808   */
809  private void checkVerifiedPasswordPolicyState(
810      Entry userEntry, SASLMechanismHandler<?> saslHandler)
811      throws DirectoryException
812  {
813    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
814    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
815
816    // Check to see if the user is administratively disabled or locked.
817    if (pwPolicyState.isDisabled())
818    {
819      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
820                                   ERR_BIND_OPERATION_ACCOUNT_DISABLED.get());
821    }
822    else if (pwPolicyState.isAccountExpired())
823    {
824      LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get();
825      pwPolicyState.generateAccountStatusNotification(
826           AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m,
827           AccountStatusNotification.createProperties(pwPolicyState,
828                 false, -1, null, null));
829
830      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
831    }
832    else if (pwPolicyState.lockedDueToFailures())
833    {
834      if (pwPolicyErrorType == null)
835      {
836        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
837      }
838
839      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
840                     ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get());
841    }
842    else if (pwPolicyState.lockedDueToIdleInterval())
843    {
844      if (pwPolicyErrorType == null)
845      {
846        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
847      }
848
849      LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get();
850      pwPolicyState.generateAccountStatusNotification(
851           AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m,
852           AccountStatusNotification.createProperties(pwPolicyState, false, -1,
853                                                      null, null));
854
855      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
856    }
857
858    // If it's a simple bind, or if it's a password-based SASL bind, then
859    // perform a number of password-based checks.
860    boolean isSASLBind = saslHandler != null;
861    if (!isSASLBind || saslHandler.isPasswordBased(saslMechanism))
862    {
863      // Check to see if the account is locked due to the maximum reset age.
864      if (pwPolicyState.lockedDueToMaximumResetAge())
865      {
866        if (pwPolicyErrorType == null)
867        {
868          pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
869        }
870
871        LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get();
872        pwPolicyState.generateAccountStatusNotification(
873             AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m,
874             AccountStatusNotification.createProperties(pwPolicyState, false,
875                                                        -1, null, null));
876
877        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
878      }
879
880      // Determine whether the password is expired, or whether the user
881      // should be warned about an upcoming expiration.
882      if (pwPolicyState.isPasswordExpired())
883      {
884        if (pwPolicyErrorType == null)
885        {
886          pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
887        }
888
889        int maxGraceLogins = policy.getGraceLoginCount();
890        if (maxGraceLogins > 0 && pwPolicyState.mayUseGraceLogin())
891        {
892          List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
893          if (graceLoginTimes == null ||
894              graceLoginTimes.size() < maxGraceLogins)
895          {
896            isGraceLogin       = true;
897            mustChangePassword = true;
898
899            if (pwPolicyWarningType == null)
900            {
901              pwPolicyWarningType =
902                   PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
903              pwPolicyWarningValue = maxGraceLogins -
904                                     (graceLoginTimes.size() + 1);
905            }
906          }
907          else
908          {
909            LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get();
910
911            pwPolicyState.generateAccountStatusNotification(
912                 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
913                 AccountStatusNotification.createProperties(pwPolicyState,
914                                                            false, -1, null,
915                                                            null));
916
917            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
918          }
919        }
920        else
921        {
922          LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get();
923
924          pwPolicyState.generateAccountStatusNotification(
925               AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
926               AccountStatusNotification.createProperties(pwPolicyState, false,
927                                                          -1, null, null));
928
929          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
930        }
931      }
932      else if (pwPolicyState.shouldWarn())
933      {
934        int numSeconds = pwPolicyState.getSecondsUntilExpiration();
935
936        if (pwPolicyWarningType == null)
937        {
938          pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
939          pwPolicyWarningValue = numSeconds;
940        }
941
942        isFirstWarning = pwPolicyState.isFirstWarning();
943      }
944
945      // Check to see if the user's password has been reset.
946      if (pwPolicyState.mustChangePassword())
947      {
948        mustChangePassword = true;
949
950        if (pwPolicyErrorType == null)
951        {
952          pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
953        }
954      }
955    }
956  }
957
958  /**
959   * Sets resource limits for the authenticated user.
960   *
961   * @param  userEntry  The entry for the authenticated user.
962   */
963  private void setResourceLimits(Entry userEntry)
964  {
965    // See if the user's entry contains a custom size limit.
966    Integer customSizeLimit =
967        getIntegerUserAttribute(userEntry, OP_ATTR_USER_SIZE_LIMIT,
968            WARN_BIND_MULTIPLE_USER_SIZE_LIMITS,
969            WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT);
970    if (customSizeLimit != null)
971    {
972      sizeLimit = customSizeLimit;
973    }
974
975    // See if the user's entry contains a custom time limit.
976    Integer customTimeLimit =
977        getIntegerUserAttribute(userEntry, OP_ATTR_USER_TIME_LIMIT,
978            WARN_BIND_MULTIPLE_USER_TIME_LIMITS,
979            WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT);
980    if (customTimeLimit != null)
981    {
982      timeLimit = customTimeLimit;
983    }
984
985    // See if the user's entry contains a custom idle time limit.
986    // idleTimeLimit = 1000L * Long.parseLong(v.toString());
987    Integer customIdleTimeLimitInSec =
988        getIntegerUserAttribute(userEntry, OP_ATTR_USER_IDLE_TIME_LIMIT,
989            WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS,
990            WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT);
991    if (customIdleTimeLimitInSec != null)
992    {
993      idleTimeLimit = 1000L * customIdleTimeLimitInSec;
994    }
995
996    // See if the user's entry contains a custom lookthrough limit.
997    Integer customLookthroughLimit =
998        getIntegerUserAttribute(userEntry, OP_ATTR_USER_LOOKTHROUGH_LIMIT,
999            WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS,
1000            WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT);
1001    if (customLookthroughLimit != null)
1002    {
1003      lookthroughLimit = customLookthroughLimit;
1004    }
1005  }
1006
1007  private Integer getIntegerUserAttribute(Entry userEntry,
1008      String attributeTypeName,
1009      Arg1<Object> nonUniqueAttributeMessage,
1010      Arg2<Object, Object> cannotProcessAttributeMessage)
1011  {
1012    AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attributeTypeName);
1013    List<Attribute> attrList = userEntry.getAttribute(attrType);
1014    if (attrList.size() == 1)
1015    {
1016      Attribute a = attrList.get(0);
1017      if (a.size() == 1)
1018      {
1019        ByteString v = a.iterator().next();
1020        try
1021        {
1022          return Integer.valueOf(v.toString());
1023        }
1024        catch (Exception e)
1025        {
1026          logger.traceException(e);
1027          logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName()));
1028        }
1029      }
1030      else if (a.size() > 1)
1031      {
1032        logger.error(nonUniqueAttributeMessage.get(userEntry.getName()));
1033      }
1034    }
1035    return null;
1036  }
1037}