/** * A utility method which may be used by implementations in order to obtain the value of the * specified attribute from the provided entry as a time in generalized time format. * * @param entry The entry whose attribute is to be parsed as a boolean. * @param attributeType The attribute type whose value should be parsed as a generalized time * value. * @return The requested time, or -1 if it could not be determined. * @throws DirectoryException If a problem occurs while attempting to decode the value as a * generalized time. */ protected static final long getGeneralizedTime( final Entry entry, final AttributeType attributeType) throws DirectoryException { long timeValue = -1; final List<Attribute> attrList = entry.getAttribute(attributeType); if (attrList != null) { for (final Attribute a : attrList) { if (a.isEmpty()) { continue; } final AttributeValue v = a.iterator().next(); try { timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(v.getNormalizedValue()); } catch (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); TRACER.debugWarning( "Unable to decode value %s for attribute %s " + "in user entry %s: %s", v.getValue().toString(), attributeType.getNameOrOID(), entry.getDN().toString(), stackTraceToSingleLineString(e)); } final Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.get( v.getValue().toString(), attributeType.getNameOrOID(), entry.getDN().toString(), String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e); } break; } } if (timeValue == -1) { if (debugEnabled()) { TRACER.debugInfo( "Returning -1 because attribute %s does not " + "exist in user entry %s", attributeType.getNameOrOID(), entry.getDN().toString()); } } // FIXME: else to be consistent... return timeValue; }
/** * Validates a number of password policy state constraints for the user. This will be called * before the offered credentials are checked. * * @param userEntry The entry for the user that is authenticating. * @param saslHandler The SASL mechanism handler if this is a SASL bind, or {@code null} for a * simple bind. * @throws DirectoryException If a problem occurs that should cause the bind to fail. */ protected void checkUnverifiedPasswordPolicyState( Entry userEntry, SASLMechanismHandler<?> saslHandler) throws DirectoryException { PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); boolean isSASLBind = saslHandler != null; // If the password policy is configured to track authentication failures or // keep the last login time and the associated backend is disabled, then we // may need to reject the bind immediately. if ((policy.getStateUpdateFailurePolicy() == PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && ((policy.getLockoutFailureCount() > 0) || ((policy.getLastLoginTimeAttribute() != null) && (policy.getLastLoginTimeFormat() != null))) && ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) || (backend.getWritabilityMode() == WritabilityMode.DISABLED))) { // This policy isn't applicable to root users, so if it's a root // user then ignore it. if (!DirectoryServer.isRootDN(userEntry.getName())) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName())); } } // Check to see if the authentication must be done in a secure // manner. If so, then the client connection must be secure. if (policy.isRequireSecureAuthentication() && !clientConnection.isSecure()) { if (isSASLBind) { if (!saslHandler.isSecure(saslMechanism)) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName())); } } else { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get()); } } }
/** * A utility method which may be used by implementations in order to obtain the value of the * specified attribute from the provided entry as a boolean. * * @param entry The entry whose attribute is to be parsed as a boolean. * @param attributeType The attribute type whose value should be parsed as a boolean. * @return The attribute's value represented as a ConditionResult value, or * ConditionResult.UNDEFINED if the specified attribute does not exist in the entry. * @throws DirectoryException If the value cannot be decoded as a boolean. */ protected static final ConditionResult getBoolean( final Entry entry, final AttributeType attributeType) throws DirectoryException { final List<Attribute> attrList = entry.getAttribute(attributeType); if (attrList != null) { for (final Attribute a : attrList) { if (a.isEmpty()) { continue; } final String valueString = toLowerCase(a.iterator().next().getValue().toString()); if (valueString.equals("true") || valueString.equals("yes") || valueString.equals("on") || valueString.equals("1")) { if (debugEnabled()) { TRACER.debugInfo( "Attribute %s resolves to true for user entry " + "%s", attributeType.getNameOrOID(), entry.getDN().toString()); } return ConditionResult.TRUE; } if (valueString.equals("false") || valueString.equals("no") || valueString.equals("off") || valueString.equals("0")) { if (debugEnabled()) { TRACER.debugInfo( "Attribute %s resolves to false for user " + "entry %s", attributeType.getNameOrOID(), entry.getDN().toString()); } return ConditionResult.FALSE; } if (debugEnabled()) { TRACER.debugError( "Unable to resolve value %s for attribute %s " + "in user entry %s as a Boolean.", valueString, attributeType.getNameOrOID(), entry.getDN().toString()); } final Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN.get( valueString, attributeType.getNameOrOID(), entry.getDN().toString()); throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); } } if (debugEnabled()) { TRACER.debugInfo( "Returning %s because attribute %s does not exist " + "in user entry %s", ConditionResult.UNDEFINED.toString(), attributeType.getNameOrOID(), entry.getDN().toString()); } return ConditionResult.UNDEFINED; }
/** * Returns {@code true} if this authentication policy state is associated with a user whose * account has been administratively disabled. * * <p>The default implementation is use the value of the "ds-pwp-account-disable" attribute in the * user's entry. * * @return {@code true} if this authentication policy state is associated with a user whose * account has been administratively disabled. */ public boolean isDisabled() { final AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true); try { isDisabled = getBoolean(userEntry, type); } catch (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } isDisabled = ConditionResult.TRUE; if (debugEnabled()) { TRACER.debugWarning( "User %s is considered administratively " + "disabled because an error occurred while " + "attempting to make the determination: %s.", userEntry.getDN().toString(), stackTraceToSingleLineString(e)); } return true; } if (isDisabled == ConditionResult.UNDEFINED) { isDisabled = ConditionResult.FALSE; if (debugEnabled()) { TRACER.debugInfo( "User %s is not administratively disabled since " + "the attribute \"%s\" is not present in the entry.", userEntry.getDN().toString(), OP_ATTR_ACCOUNT_DISABLED); } return false; } if (debugEnabled()) { TRACER.debugInfo( "User %s %s administratively disabled.", userEntry.getDN().toString(), ((isDisabled == ConditionResult.TRUE) ? " is" : " is not")); } return isDisabled == ConditionResult.TRUE; }
private Integer getIntegerUserAttribute( Entry userEntry, String attributeTypeName, Arg1<Object> nonUniqueAttributeMessage, Arg2<Object, Object> cannotProcessAttributeMessage) { AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(attributeTypeName); List<Attribute> attrList = userEntry.getAttribute(attrType); if (attrList != null && attrList.size() == 1) { Attribute a = attrList.get(0); if (a.size() == 1) { ByteString v = a.iterator().next(); try { return Integer.valueOf(v.toString()); } catch (Exception e) { logger.traceException(e); logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName())); } } else if (a.size() > 1) { logger.error(nonUniqueAttributeMessage.get(userEntry.getName())); } } return null; }
private DN getDN(Entry e) { return e != null ? e.getDN() : DN.nullDN(); }
/** * Performs the processing necessary for a SASL bind operation. * * @return {@code true} if processing should continue for the operation, or {@code false} if not. * @throws DirectoryException If a problem occurs that should cause the bind operation to fail. */ private boolean processSASLBind() throws DirectoryException { // Get the appropriate authentication handler for this request based // on the SASL mechanism. If there is none, then fail. SASLMechanismHandler<?> saslHandler = DirectoryServer.getSASLMechanismHandler(saslMechanism); if (saslHandler == null) { throw new DirectoryException( ResultCode.AUTH_METHOD_NOT_SUPPORTED, ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(saslMechanism)); } // Check to see if the client has sufficient permission to perform the bind. // NYI // Invoke pre-operation plugins. if (!invokePreOpPlugins()) { return false; } // Actually process the SASL bind. saslHandler.processSASLBind(this); // If the server is operating in lockdown mode, then we will need to // ensure that the authentication was successful and performed as a // root user to continue. Entry saslAuthUserEntry = getSASLAuthUserEntry(); if (DirectoryServer.lockdownMode()) { ResultCode resultCode = getResultCode(); if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS && (resultCode != ResultCode.SUCCESS || saslAuthUserEntry == null || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN))) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); } } // Create the password policy state object. if (saslAuthUserEntry != null) { setUserEntryDN(saslAuthUserEntry.getName()); // FIXME -- Need to have a way to enable debugging. authPolicyState = AuthenticationPolicyState.forUser(saslAuthUserEntry, false); if (authPolicyState.isPasswordPolicy()) { // Account is managed locally: perform password policy checks that can // be completed before we have checked authentication was successful. checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); } } // Determine whether the authentication was successful and perform // any remaining password policy processing accordingly. ResultCode resultCode = getResultCode(); if (resultCode == ResultCode.SUCCESS) { if (authPolicyState != null && authPolicyState.isPasswordPolicy()) { checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler); PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; if (saslHandler.isPasswordBased(saslMechanism) && pwPolicyState.mustChangePassword()) { mustChangePassword = true; } if (isFirstWarning) { pwPolicyState.setWarnedTime(); int numSeconds = pwPolicyState.getSecondsUntilExpiration(); LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(secondsToTimeString(numSeconds)); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRING, saslAuthUserEntry, m, AccountStatusNotification.createProperties( pwPolicyState, false, numSeconds, null, null)); } if (isGraceLogin) { pwPolicyState.updateGraceLoginTimes(); } pwPolicyState.setLastLoginTime(); } // Set appropriate resource limits for the user (note that SASL ANONYMOUS // does not have a user). if (saslAuthUserEntry != null) { setResourceLimits(saslAuthUserEntry); } } else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) { // FIXME -- Is any special processing needed here? return false; } else { if (authPolicyState != null && authPolicyState.isPasswordPolicy()) { PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; if (saslHandler.isPasswordBased(saslMechanism) && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0) { generateAccountStatusNotificationForLockedBindAccount(saslAuthUserEntry, pwPolicyState); } } } return true; }
/** * Performs the processing necessary for a simple bind operation. * * @return {@code true} if processing should continue for the operation, or {@code false} if not. * @throws DirectoryException If a problem occurs that should cause the bind operation to fail. */ protected boolean processSimpleBind() throws DirectoryException { // See if this is an anonymous bind. If so, then determine whether // to allow it. ByteString simplePassword = getSimplePassword(); if (simplePassword == null || simplePassword.length() == 0) { return processAnonymousSimpleBind(); } // See if the bind DN is actually one of the alternate root DNs // defined in the server. If so, then replace it with the actual DN // for that user. DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN); if (actualRootDN != null) { bindDN = actualRootDN; } Entry userEntry; try { userEntry = backend.getEntry(bindDN); } catch (DirectoryException de) { logger.traceException(de); userEntry = null; if (de.getResultCode() == ResultCode.REFERRAL) { // Re-throw referral exceptions - these should be passed back // to the client. throw de; } else { // Replace other exceptions in case they expose any sensitive // information. throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, de.getMessageObject()); } } if (userEntry == null) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_UNKNOWN_USER.get()); } else { setUserEntryDN(userEntry.getName()); } // Check to see if the user has a password. If not, then fail. // FIXME -- We need to have a way to enable/disable debugging. authPolicyState = AuthenticationPolicyState.forUser(userEntry, false); if (authPolicyState.isPasswordPolicy()) { // Account is managed locally. PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState; PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy(); AttributeType pwType = policy.getPasswordAttribute(); List<Attribute> pwAttr = userEntry.getAttribute(pwType); if (pwAttr == null || pwAttr.isEmpty()) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_NO_PASSWORD.get()); } // Perform a number of password policy state checks for the // non-authenticated user. checkUnverifiedPasswordPolicyState(userEntry, null); // Invoke pre-operation plugins. if (!invokePreOpPlugins()) { return false; } // Determine whether the provided password matches any of the stored // passwords for the user. if (pwPolicyState.passwordMatches(simplePassword)) { setResultCode(ResultCode.SUCCESS); checkVerifiedPasswordPolicyState(userEntry, null); if (DirectoryServer.lockdownMode() && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); } setAuthenticationInfo( new AuthenticationInfo( userEntry, getBindDN(), DirectoryServer.isRootDN(userEntry.getName()))); // Set resource limits for the authenticated user. setResourceLimits(userEntry); // Perform any remaining processing for a successful simple // authentication. pwPolicyState.handleDeprecatedStorageSchemes(simplePassword); pwPolicyState.clearFailureLockout(); if (isFirstWarning) { pwPolicyState.setWarnedTime(); int numSeconds = pwPolicyState.getSecondsUntilExpiration(); LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(secondsToTimeString(numSeconds)); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m, AccountStatusNotification.createProperties( pwPolicyState, false, numSeconds, null, null)); } if (isGraceLogin) { pwPolicyState.updateGraceLoginTimes(); } pwPolicyState.setLastLoginTime(); } else { setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); if (policy.getLockoutFailureCount() > 0) { generateAccountStatusNotificationForLockedBindAccount(userEntry, pwPolicyState); } } } else { // Check to see if the user is administratively disabled or locked. if (authPolicyState.isDisabled()) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_ACCOUNT_DISABLED.get()); } // Invoke pre-operation plugins. if (!invokePreOpPlugins()) { return false; } if (authPolicyState.passwordMatches(simplePassword)) { setResultCode(ResultCode.SUCCESS); if (DirectoryServer.lockdownMode() && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) { throw new DirectoryException( ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); } setAuthenticationInfo( new AuthenticationInfo( userEntry, getBindDN(), DirectoryServer.isRootDN(userEntry.getName()))); // Set resource limits for the authenticated user. setResourceLimits(userEntry); } else { setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); } } return true; }