/** * Test entry. * * @throws Exception If the test failed unexpectedly. */ @Test public void testEntryToAndFromDatabase() throws Exception { ensureServerIsUpAndRunning(); // Convert the test LDIF string to a byte array byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString); try (final LDIFReader reader = new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) { Entry entryBefore, entryAfter; while ((entryBefore = reader.readEntry(false)) != null) { ByteString bytes = ID2Entry.entryToDatabase(entryBefore, new DataConfig(false, false, null)); entryAfter = ID2Entry.entryFromDatabase(bytes, DirectoryServer.getDefaultCompressedSchema()); // check DN and number of attributes assertEquals(entryBefore.getAttributes().size(), entryAfter.getAttributes().size()); assertEquals(entryBefore.getName(), entryAfter.getName()); // check the object classes were not changed for (String ocBefore : entryBefore.getObjectClasses().values()) { ObjectClass objectClass = DirectoryServer.getObjectClass(ocBefore.toLowerCase()); if (objectClass == null) { objectClass = DirectoryServer.getDefaultObjectClass(ocBefore); } String ocAfter = entryAfter.getObjectClasses().get(objectClass); assertEquals(ocBefore, ocAfter); } // check the user attributes were not changed for (AttributeType attrType : entryBefore.getUserAttributes().keySet()) { List<Attribute> listBefore = entryBefore.getAttribute(attrType); List<Attribute> listAfter = entryAfter.getAttribute(attrType); assertThat(listBefore).hasSameSizeAs(listAfter); for (Attribute attrBefore : listBefore) { boolean found = false; for (Attribute attrAfter : listAfter) { if (attrAfter.optionsEqual(attrBefore.getOptions())) { // Found the corresponding attribute assertEquals(attrBefore, attrAfter); found = true; } } assertTrue(found); } } } } }
/** * Encodes this entry using the V3 encoding. * * @param buffer The buffer to encode into. * @throws DirectoryException If a problem occurs while attempting to encode the entry. */ private void encodeV1(Entry entry, ByteStringBuilder buffer) throws DirectoryException { // The version number will be one byte. buffer.appendByte(0x01); // TODO: Can we encode the DN directly into buffer? byte[] dnBytes = getBytes(entry.getName().toString()); buffer.appendBERLength(dnBytes.length); buffer.appendBytes(dnBytes); // Encode number of OCs and 0 terminated names. int i = 1; ByteStringBuilder bsb = new ByteStringBuilder(); for (String ocName : entry.getObjectClasses().values()) { bsb.appendUtf8(ocName); if (i < entry.getObjectClasses().values().size()) { bsb.appendByte(0x00); } i++; } buffer.appendBERLength(bsb.length()); buffer.appendBytes(bsb); // Encode the user attributes in the appropriate manner. encodeV1Attributes(buffer, entry.getUserAttributes()); // The operational attributes will be encoded in the same way as // the user attributes. encodeV1Attributes(buffer, entry.getOperationalAttributes()); }
/** * 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()); } } }
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; }
/** * Encodes this entry using the V3 encoding. * * @param buffer The buffer to encode into. * @throws DirectoryException If a problem occurs while attempting to encode the entry. */ private void encodeV2(Entry entry, ByteStringBuilder buffer, EntryEncodeConfig config) throws DirectoryException { // The version number will be one byte. buffer.appendByte(0x02); // Get the encoded respresentation of the config. config.encode(buffer); // If we should include the DN, then it will be encoded as a // one-to-five byte length followed by the UTF-8 byte // representation. if (!config.excludeDN()) { // TODO: Can we encode the DN directly into buffer? byte[] dnBytes = getBytes(entry.getName().toString()); buffer.appendBERLength(dnBytes.length); buffer.appendBytes(dnBytes); } // Encode the object classes in the appropriate manner. if (config.compressObjectClassSets()) { config.getCompressedSchema().encodeObjectClasses(buffer, entry.getObjectClasses()); } else { // Encode number of OCs and 0 terminated names. int i = 1; ByteStringBuilder bsb = new ByteStringBuilder(); for (String ocName : entry.getObjectClasses().values()) { bsb.appendUtf8(ocName); if (i < entry.getObjectClasses().values().size()) { bsb.appendByte(0x00); } i++; } buffer.appendBERLength(bsb.length()); buffer.appendBytes(bsb); } // Encode the user attributes in the appropriate manner. encodeV2Attributes(buffer, entry.getUserAttributes(), config); // The operational attributes will be encoded in the same way as // the user attributes. encodeV2Attributes(buffer, entry.getOperationalAttributes(), config); }
/** * Tests the entry encoding and decoding process the version 1 encoding. * * @throws Exception If the test failed unexpectedly. */ @Test(dataProvider = "encodeConfigs") public void testEntryToAndFromDatabaseV3(EntryEncodeConfig config) throws Exception { ensureServerIsUpAndRunning(); // Convert the test LDIF string to a byte array byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString); try (final LDIFReader reader = new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) { Entry entryBefore, entryAfterV3; while ((entryBefore = reader.readEntry(false)) != null) { ByteStringBuilder bsb = new ByteStringBuilder(); entryBefore.encode(bsb, config); entryAfterV3 = Entry.decode(bsb.asReader()); if (config.excludeDN()) { entryAfterV3.setDN(entryBefore.getName()); } assertEquals(entryBefore, entryAfterV3); } } }
/** {@inheritDoc} */ @Override public void processSASLBind(BindOperation bindOperation) { ExternalSASLMechanismHandlerCfg config = currentConfig; AttributeType certificateAttributeType = this.certificateAttributeType; CertificateValidationPolicy validationPolicy = this.validationPolicy; // Get the client connection used for the bind request, and get the // security manager for that connection. If either are null, then fail. ClientConnection clientConnection = bindOperation.getClientConnection(); if (clientConnection == null) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CONNECTION.get(); bindOperation.setAuthFailureReason(message); return; } if (!(clientConnection instanceof LDAPClientConnection)) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_NOT_LDAP_CLIENT_INSTANCE.get(); bindOperation.setAuthFailureReason(message); return; } LDAPClientConnection lc = (LDAPClientConnection) clientConnection; Certificate[] clientCertChain = lc.getClientCertificateChain(); if ((clientCertChain == null) || (clientCertChain.length == 0)) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CERT.get(); bindOperation.setAuthFailureReason(message); return; } // Get the certificate mapper to use to map the certificate to a user entry. DN certificateMapperDN = config.getCertificateMapperDN(); CertificateMapper<?> certificateMapper = DirectoryServer.getCertificateMapper(certificateMapperDN); // Use the Directory Server certificate mapper to map the client certificate // chain to a single user DN. Entry userEntry; try { userEntry = certificateMapper.mapCertificateToUser(clientCertChain); } catch (DirectoryException de) { logger.traceException(de); bindOperation.setResponseData(de); return; } // If the user DN is null, then we couldn't establish a mapping and // therefore the authentication failed. if (userEntry == null) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_NO_MAPPING.get(); bindOperation.setAuthFailureReason(message); return; } else { bindOperation.setSASLAuthUserEntry(userEntry); } // Get the userCertificate attribute from the user's entry for use in the // validation process. List<Attribute> certAttrList = userEntry.getAttribute(certificateAttributeType); switch (validationPolicy) { case ALWAYS: if (certAttrList == null) { if (validationPolicy == CertificateValidationPolicy.ALWAYS) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_NO_CERT_IN_ENTRY.get(userEntry.getName()); bindOperation.setAuthFailureReason(message); return; } } else { try { ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); if (!find(certAttrList, certBytes)) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); bindOperation.setAuthFailureReason(message); return; } } catch (Exception e) { logger.traceException(e); bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( userEntry.getName(), getExceptionMessage(e)); bindOperation.setAuthFailureReason(message); return; } } break; case IFPRESENT: if (certAttrList != null) { try { ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); if (!find(certAttrList, certBytes)) { bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); bindOperation.setAuthFailureReason(message); return; } } catch (Exception e) { logger.traceException(e); bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( userEntry.getName(), getExceptionMessage(e)); bindOperation.setAuthFailureReason(message); return; } } } AuthenticationInfo authInfo = new AuthenticationInfo( userEntry, SASL_MECHANISM_EXTERNAL, DirectoryServer.isRootDN(userEntry.getName())); bindOperation.setAuthenticationInfo(authInfo); bindOperation.setResultCode(ResultCode.SUCCESS); }
/** * 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; }