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.opends.server.admin.std.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          generateAccountStatusNotificationForLockedBindAccount(userEntry,
477              pwPolicyState);
478        }
479      }
480    }
481    else
482    {
483      // Check to see if the user is administratively disabled or locked.
484      if (authPolicyState.isDisabled())
485      {
486        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
487            ERR_BIND_OPERATION_ACCOUNT_DISABLED.get());
488      }
489
490      // Invoke pre-operation plugins.
491      if (!invokePreOpPlugins())
492      {
493        return false;
494      }
495
496      if (authPolicyState.passwordMatches(simplePassword))
497      {
498        setResultCode(ResultCode.SUCCESS);
499
500        if (DirectoryServer.lockdownMode()
501            && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN))
502        {
503          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
504              ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
505        }
506        setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
507            DirectoryServer.isRootDN(userEntry.getName())));
508
509        // Set resource limits for the authenticated user.
510        setResourceLimits(userEntry);
511      }
512      else
513      {
514        setResultCode(ResultCode.INVALID_CREDENTIALS);
515        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
516      }
517    }
518
519    return true;
520  }
521
522  /**
523   * Performs the processing necessary for an anonymous simple bind.
524   *
525   * @return  {@code true} if processing should continue for the operation, or
526   *          {@code false} if not.
527   * @throws  DirectoryException  If a problem occurs that should cause the bind
528   *                              operation to fail.
529   */
530  private boolean processAnonymousSimpleBind() throws DirectoryException
531  {
532    // If the server is in lockdown mode, then fail.
533    if (DirectoryServer.lockdownMode())
534    {
535      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
536                                   ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
537    }
538
539    // If there is a bind DN, then see whether that is acceptable.
540    if (DirectoryServer.bindWithDNRequiresPassword()
541        && bindDN != null && !bindDN.isRootDN())
542    {
543      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
544                                   ERR_BIND_DN_BUT_NO_PASSWORD.get());
545    }
546
547    // Invoke pre-operation plugins.
548    if (!invokePreOpPlugins())
549    {
550      return false;
551    }
552
553    setResultCode(ResultCode.SUCCESS);
554    setAuthenticationInfo(new AuthenticationInfo());
555    return true;
556  }
557
558  /**
559   * Performs the processing necessary for a SASL bind operation.
560   *
561   * @return  {@code true} if processing should continue for the operation, or
562   *          {@code false} if not.
563   *
564   * @throws  DirectoryException  If a problem occurs that should cause the bind
565   *                              operation to fail.
566   */
567  private boolean processSASLBind() throws DirectoryException
568  {
569    // Get the appropriate authentication handler for this request based
570    // on the SASL mechanism.  If there is none, then fail.
571    SASLMechanismHandler<?> saslHandler =
572         DirectoryServer.getSASLMechanismHandler(saslMechanism);
573    if (saslHandler == null)
574    {
575      throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED,
576                     ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(
577                          saslMechanism));
578    }
579
580    // Check to see if the client has sufficient permission to perform the bind.
581    // NYI
582
583    // Invoke pre-operation plugins.
584    if (!invokePreOpPlugins())
585    {
586      return false;
587    }
588
589    // Actually process the SASL bind.
590    saslHandler.processSASLBind(this);
591
592    // If the server is operating in lockdown mode, then we will need to
593    // ensure that the authentication was successful and performed as a
594    // root user to continue.
595    Entry saslAuthUserEntry = getSASLAuthUserEntry();
596    if (DirectoryServer.lockdownMode())
597    {
598      ResultCode resultCode = getResultCode();
599      if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS
600          && (resultCode != ResultCode.SUCCESS
601              || saslAuthUserEntry == null
602              || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN)))
603      {
604        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
605                                     ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
606      }
607    }
608
609    // Create the password policy state object.
610    if (saslAuthUserEntry != null)
611    {
612      setUserEntryDN(saslAuthUserEntry.getName());
613
614      // FIXME -- Need to have a way to enable debugging.
615      authPolicyState = AuthenticationPolicyState.forUser(
616          saslAuthUserEntry, false);
617      if (authPolicyState.isPasswordPolicy())
618      {
619        // Account is managed locally: perform password policy checks that can
620        // be completed before we have checked authentication was successful.
621        checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);
622      }
623    }
624
625    // Determine whether the authentication was successful and perform
626    // any remaining password policy processing accordingly.
627    ResultCode resultCode = getResultCode();
628    if (resultCode == ResultCode.SUCCESS)
629    {
630      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
631      {
632        checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);
633
634        PasswordPolicyState pwPolicyState =
635          (PasswordPolicyState) authPolicyState;
636
637        if (saslHandler.isPasswordBased(saslMechanism) &&
638            pwPolicyState.mustChangePassword())
639        {
640          mustChangePassword = true;
641        }
642
643        if (isFirstWarning)
644        {
645          pwPolicyState.setWarnedTime();
646
647          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
648          LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(
649                                 secondsToTimeString(numSeconds));
650
651          pwPolicyState.generateAccountStatusNotification(
652               AccountStatusNotificationType.PASSWORD_EXPIRING,
653               saslAuthUserEntry, m,
654               AccountStatusNotification.createProperties(pwPolicyState,
655                     false, numSeconds, null, null));
656        }
657
658        if (isGraceLogin)
659        {
660          pwPolicyState.updateGraceLoginTimes();
661        }
662
663        pwPolicyState.setLastLoginTime();
664      }
665
666      // Set appropriate resource limits for the user (note that SASL ANONYMOUS
667      // does not have a user).
668      if (saslAuthUserEntry != null)
669      {
670        setResourceLimits(saslAuthUserEntry);
671      }
672    }
673    else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
674    {
675      // FIXME -- Is any special processing needed here?
676      return false;
677    }
678    else
679    {
680      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
681      {
682        PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
683
684        if (saslHandler.isPasswordBased(saslMechanism)
685            && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0)
686        {
687          generateAccountStatusNotificationForLockedBindAccount(
688              saslAuthUserEntry, pwPolicyState);
689        }
690      }
691    }
692
693    return true;
694  }
695
696  private void generateAccountStatusNotificationForLockedBindAccount(
697      Entry userEntry, PasswordPolicyState pwPolicyState)
698  {
699    pwPolicyState.updateAuthFailureTimes();
700    if (pwPolicyState.lockedDueToFailures())
701    {
702      AccountStatusNotificationType notificationType;
703      boolean tempLocked;
704      LocalizableMessage m;
705
706      int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
707      if (lockoutDuration > -1)
708      {
709        notificationType = AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
710        tempLocked = true;
711        m =
712            ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED
713                .get(secondsToTimeString(lockoutDuration));
714      }
715      else
716      {
717        notificationType = AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
718        tempLocked = false;
719        m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
720      }
721
722      pwPolicyState.generateAccountStatusNotification(notificationType,
723          userEntry, m, AccountStatusNotification.createProperties(
724              pwPolicyState, tempLocked, -1, null, null));
725    }
726  }
727
728  private boolean invokePreOpPlugins()
729  {
730    executePostOpPlugins = true;
731    return processOperationResult(this, pluginConfigManager.invokePreOperationBindPlugins(this));
732  }
733
734  /**
735   * Validates a number of password policy state constraints for the user. This
736   * will be called before the offered credentials are checked.
737   *
738   * @param userEntry
739   *          The entry for the user that is authenticating.
740   * @param saslHandler
741   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
742   *          for a simple bind.
743   * @throws DirectoryException
744   *           If a problem occurs that should cause the bind to fail.
745   */
746  private void checkUnverifiedPasswordPolicyState(
747      Entry userEntry, SASLMechanismHandler<?> saslHandler)
748      throws DirectoryException
749  {
750    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
751    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
752
753
754    // If the password policy is configured to track authentication failures or
755    // keep the last login time and the associated backend is disabled, then we
756    // may need to reject the bind immediately.
757    if ((policy.getStateUpdateFailurePolicy() ==
758         PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
759        ((policy.getLockoutFailureCount() > 0) ||
760         ((policy.getLastLoginTimeAttribute() != null) &&
761          (policy.getLastLoginTimeFormat() != null))) &&
762        ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) ||
763         (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
764    {
765      // This policy isn't applicable to root users, so if it's a root
766      // user then ignore it.
767      if (! DirectoryServer.isRootDN(userEntry.getName()))
768      {
769        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
770            ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName()));
771      }
772    }
773
774    // Check to see if the authentication must be done in a secure
775    // manner.  If so, then the client connection must be secure.
776    if (policy.isRequireSecureAuthentication()
777        && !clientConnection.isSecure())
778    {
779      boolean isSASLBind = saslHandler != null;
780      if (isSASLBind)
781      {
782        if (! saslHandler.isSecure(saslMechanism))
783        {
784          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
785              ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName()));
786        }
787      }
788      else
789      {
790        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
791                       ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get());
792      }
793    }
794  }
795
796  /**
797   * Perform policy checks for accounts when the credentials are correct.
798   *
799   * @param userEntry
800   *          The entry for the user that is authenticating.
801   * @param saslHandler
802   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
803   *          for a simple bind.
804   * @throws DirectoryException
805   *           If a problem occurs that should cause the bind to fail.
806   */
807  private void checkVerifiedPasswordPolicyState(
808      Entry userEntry, SASLMechanismHandler<?> saslHandler)
809      throws DirectoryException
810  {
811    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
812    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
813
814    // Check to see if the user is administratively disabled or locked.
815    if (pwPolicyState.isDisabled())
816    {
817      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
818                                   ERR_BIND_OPERATION_ACCOUNT_DISABLED.get());
819    }
820    else if (pwPolicyState.isAccountExpired())
821    {
822      LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get();
823      pwPolicyState.generateAccountStatusNotification(
824           AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m,
825           AccountStatusNotification.createProperties(pwPolicyState,
826                 false, -1, null, null));
827
828      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
829    }
830    else if (pwPolicyState.lockedDueToFailures())
831    {
832      if (pwPolicyErrorType == null)
833      {
834        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
835      }
836
837      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
838                     ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get());
839    }
840    else if (pwPolicyState.lockedDueToIdleInterval())
841    {
842      if (pwPolicyErrorType == null)
843      {
844        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
845      }
846
847      LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get();
848      pwPolicyState.generateAccountStatusNotification(
849           AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m,
850           AccountStatusNotification.createProperties(pwPolicyState, false, -1,
851                                                      null, null));
852
853      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
854    }
855
856    // If it's a simple bind, or if it's a password-based SASL bind, then
857    // perform a number of password-based checks.
858    boolean isSASLBind = saslHandler != null;
859    if (!isSASLBind || saslHandler.isPasswordBased(saslMechanism))
860    {
861      // Check to see if the account is locked due to the maximum reset age.
862      if (pwPolicyState.lockedDueToMaximumResetAge())
863      {
864        if (pwPolicyErrorType == null)
865        {
866          pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
867        }
868
869        LocalizableMessage m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get();
870        pwPolicyState.generateAccountStatusNotification(
871             AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m,
872             AccountStatusNotification.createProperties(pwPolicyState, false,
873                                                        -1, null, null));
874
875        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
876      }
877
878      // Determine whether the password is expired, or whether the user
879      // should be warned about an upcoming expiration.
880      if (pwPolicyState.isPasswordExpired())
881      {
882        if (pwPolicyErrorType == null)
883        {
884          pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
885        }
886
887        int maxGraceLogins = policy.getGraceLoginCount();
888        if (maxGraceLogins > 0 && pwPolicyState.mayUseGraceLogin())
889        {
890          List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
891          if (graceLoginTimes == null ||
892              graceLoginTimes.size() < maxGraceLogins)
893          {
894            isGraceLogin       = true;
895            mustChangePassword = true;
896
897            if (pwPolicyWarningType == null)
898            {
899              pwPolicyWarningType =
900                   PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
901              pwPolicyWarningValue = maxGraceLogins -
902                                     (graceLoginTimes.size() + 1);
903            }
904          }
905          else
906          {
907            LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get();
908
909            pwPolicyState.generateAccountStatusNotification(
910                 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
911                 AccountStatusNotification.createProperties(pwPolicyState,
912                                                            false, -1, null,
913                                                            null));
914
915            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
916          }
917        }
918        else
919        {
920          LocalizableMessage m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get();
921
922          pwPolicyState.generateAccountStatusNotification(
923               AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
924               AccountStatusNotification.createProperties(pwPolicyState, false,
925                                                          -1, null, null));
926
927          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
928        }
929      }
930      else if (pwPolicyState.shouldWarn())
931      {
932        int numSeconds = pwPolicyState.getSecondsUntilExpiration();
933
934        if (pwPolicyWarningType == null)
935        {
936          pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
937          pwPolicyWarningValue = numSeconds;
938        }
939
940        isFirstWarning = pwPolicyState.isFirstWarning();
941      }
942
943      // Check to see if the user's password has been reset.
944      if (pwPolicyState.mustChangePassword())
945      {
946        mustChangePassword = true;
947
948        if (pwPolicyErrorType == null)
949        {
950          pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
951        }
952      }
953    }
954  }
955
956  /**
957   * Sets resource limits for the authenticated user.
958   *
959   * @param  userEntry  The entry for the authenticated user.
960   */
961  private void setResourceLimits(Entry userEntry)
962  {
963    // See if the user's entry contains a custom size limit.
964    Integer customSizeLimit =
965        getIntegerUserAttribute(userEntry, OP_ATTR_USER_SIZE_LIMIT,
966            WARN_BIND_MULTIPLE_USER_SIZE_LIMITS,
967            WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT);
968    if (customSizeLimit != null)
969    {
970      sizeLimit = customSizeLimit;
971    }
972
973    // See if the user's entry contains a custom time limit.
974    Integer customTimeLimit =
975        getIntegerUserAttribute(userEntry, OP_ATTR_USER_TIME_LIMIT,
976            WARN_BIND_MULTIPLE_USER_TIME_LIMITS,
977            WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT);
978    if (customTimeLimit != null)
979    {
980      timeLimit = customTimeLimit;
981    }
982
983    // See if the user's entry contains a custom idle time limit.
984    // idleTimeLimit = 1000L * Long.parseLong(v.toString());
985    Integer customIdleTimeLimitInSec =
986        getIntegerUserAttribute(userEntry, OP_ATTR_USER_IDLE_TIME_LIMIT,
987            WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS,
988            WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT);
989    if (customIdleTimeLimitInSec != null)
990    {
991      idleTimeLimit = 1000L * customIdleTimeLimitInSec;
992    }
993
994    // See if the user's entry contains a custom lookthrough limit.
995    Integer customLookthroughLimit =
996        getIntegerUserAttribute(userEntry, OP_ATTR_USER_LOOKTHROUGH_LIMIT,
997            WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS,
998            WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT);
999    if (customLookthroughLimit != null)
1000    {
1001      lookthroughLimit = customLookthroughLimit;
1002    }
1003  }
1004
1005  private Integer getIntegerUserAttribute(Entry userEntry,
1006      String attributeTypeName,
1007      Arg1<Object> nonUniqueAttributeMessage,
1008      Arg2<Object, Object> cannotProcessAttributeMessage)
1009  {
1010    AttributeType attrType = DirectoryServer.getAttributeType(attributeTypeName);
1011    List<Attribute> attrList = userEntry.getAttribute(attrType);
1012    if (attrList.size() == 1)
1013    {
1014      Attribute a = attrList.get(0);
1015      if (a.size() == 1)
1016      {
1017        ByteString v = a.iterator().next();
1018        try
1019        {
1020          return Integer.valueOf(v.toString());
1021        }
1022        catch (Exception e)
1023        {
1024          logger.traceException(e);
1025          logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName()));
1026        }
1027      }
1028      else if (a.size() > 1)
1029      {
1030        logger.error(nonUniqueAttributeMessage.get(userEntry.getName()));
1031      }
1032    }
1033    return null;
1034  }
1035}