if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.ACCOUNT_LOCKED;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
Message message = ERR_EXTOP_PASSMOD_ACCOUNT_DISABLED.get();
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(message);
return;
}
else if (selfChange &&
(pwPolicyState.lockedDueToFailures() ||
pwPolicyState.lockedDueToIdleInterval() ||
pwPolicyState.lockedDueToMaximumResetAge()))
{
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.ACCOUNT_LOCKED;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
Message message = ERR_EXTOP_PASSMOD_ACCOUNT_LOCKED.get();
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(message);
return;
}
// If the current password was provided, then we'll need to verify whether
// it was correct. If it wasn't provided but this is a self change, then
// make sure that's OK.
if (oldPassword == null)
{
if (selfChange && pwPolicyState.getPolicy().requireCurrentPassword())
{
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_REQUIRE_CURRENT_PW.get());
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
return;
}
}
else
{
if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
(! operation.getClientConnection().isSecure()))
{
operation.setResultCode(ResultCode.CONFIDENTIALITY_REQUIRED);
operation.appendAdditionalLogMessage(
ERR_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED.get());
return;
}
if (pwPolicyState.passwordMatches(oldPassword))
{
pwPolicyState.setLastLoginTime();
}
else
{
operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
operation.appendAdditionalLogMessage(
ERR_EXTOP_PASSMOD_INVALID_OLD_PASSWORD.get());
pwPolicyState.updateAuthFailureTimes();
List<Modification> mods = pwPolicyState.getModifications();
if (! mods.isEmpty())
{
InternalClientConnection conn =
InternalClientConnection.getRootConnection();
conn.processModify(userDN, mods);
}
return;
}
}
// If it is a self password change and we don't allow that, then reject
// the request.
if (selfChange &&
(! pwPolicyState.getPolicy().allowUserPasswordChanges()))
{
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED.get());
return;
}
// If we require secure password changes and the connection isn't secure,
// then reject the request.
if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
(! operation.getClientConnection().isSecure()))
{
operation.setResultCode(ResultCode.CONFIDENTIALITY_REQUIRED);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED.get());
return;
}
// If it's a self-change request and the user is within the minimum age,
// then reject it.
if (selfChange && pwPolicyState.isWithinMinimumAge())
{
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.PASSWORD_TOO_YOUNG;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(ERR_EXTOP_PASSMOD_IN_MIN_AGE.get());
return;
}
// If the user's password is expired and it's a self-change request, then
// see if that's OK.
if ((selfChange && pwPolicyState.isPasswordExpired() &&
(! pwPolicyState.getPolicy().allowExpiredPasswordChanges())))
{
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.PASSWORD_EXPIRED;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED.get());
return;
}
// If the a new password was provided, then peform any appropriate
// validation on it. If not, then see if we can generate one.
boolean generatedPassword = false;
boolean isPreEncoded = false;
if (newPassword == null)
{
try
{
newPassword = pwPolicyState.generatePassword();
if (newPassword == null)
{
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_NO_PW_GENERATOR.get());
return;
}
else
{
generatedPassword = true;
}
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
operation.setResultCode(de.getResultCode());
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_CANNOT_GENERATE_PW.get(
de.getMessageObject()));
return;
}
}
else
{
if (pwPolicyState.passwordIsPreEncoded(newPassword))
{
// The password modify extended operation isn't intended to be invoked
// by an internal operation or during synchronization, so we don't
// need to check for those cases.
isPreEncoded = true;
if (! pwPolicyState.getPolicy().allowPreEncodedPasswords())
{
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED.get());
return;
}
}
else
{
// Run the new password through the set of password validators.
if (selfChange ||
(! pwPolicyState.getPolicy().skipValidationForAdministrators()))
{
HashSet<ByteString> clearPasswords;
if (oldPassword == null)
{
clearPasswords =
new HashSet<ByteString>(pwPolicyState.getClearPasswords());
}
else
{
clearPasswords = new HashSet<ByteString>();
clearPasswords.add(oldPassword);
for (ByteString pw : pwPolicyState.getClearPasswords())
{
if (! pw.equals(oldPassword))
{
clearPasswords.add(pw);
}
}
}
MessageBuilder invalidReason = new MessageBuilder();
if (! pwPolicyState.passwordIsAcceptable(operation, userEntry,
newPassword,
clearPasswords,
invalidReason))
{
if (pwPolicyRequested)
{
pwPolicyErrorType =
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_UNACCEPTABLE_PW.get(
String.valueOf(invalidReason)));
return;
}
}
// Prepare to update the password history, if necessary.
if (pwPolicyState.maintainHistory())
{
if (pwPolicyState.isPasswordInHistory(newPassword))
{
if (selfChange || (! pwPolicyState.getPolicy().
skipValidationForAdministrators()))
{
operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_PW_IN_HISTORY.get());
return;
}
}
else
{
pwPolicyState.updatePasswordHistory();
}
}
}
}
// Get the encoded forms of the new password.
List<ByteString> encodedPasswords;
if (isPreEncoded)
{
encodedPasswords = new ArrayList<ByteString>(1);
encodedPasswords.add(newPassword);
}
else
{
try
{
encodedPasswords = pwPolicyState.encodePassword(newPassword);
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
operation.setResultCode(de.getResultCode());
operation.appendErrorMessage(
ERR_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD.get(
de.getMessageObject()));
return;
}
}
// If the current password was provided, then remove all matching values
// from the user's entry and replace them with the new password.
// Otherwise replace all password values.
AttributeType attrType = pwPolicyState.getPolicy().getPasswordAttribute();
List<Modification> modList = new ArrayList<Modification>();
if (oldPassword != null)
{
// Remove all existing encoded values that match the old password.
Set<AttributeValue> existingValues = pwPolicyState.getPasswordValues();
LinkedHashSet<AttributeValue> deleteValues =
new LinkedHashSet<AttributeValue>(existingValues.size());
if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
{
for (AttributeValue v : existingValues)
{
try
{
StringBuilder[] components =
AuthPasswordSyntax.decodeAuthPassword(v.getValue().toString());
PasswordStorageScheme<?> scheme =
DirectoryServer.getAuthPasswordStorageScheme(
components[0].toString());
if (scheme == null)
{
// The password is encoded using an unknown scheme. Remove it
// from the user's entry.
deleteValues.add(v);
}
else
{
if (scheme.authPasswordMatches(oldPassword,
components[1].toString(),
components[2].toString()))
{
deleteValues.add(v);
}
}
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
// We couldn't decode the provided password value, so remove it
// from the user's entry.
deleteValues.add(v);
}
}
}
else
{
for (AttributeValue v : existingValues)
{
try
{
String[] components =
UserPasswordSyntax.decodeUserPassword(v.getValue().toString());
PasswordStorageScheme<?> scheme =
DirectoryServer.getPasswordStorageScheme(
toLowerCase(components[0]));
if (scheme == null)
{
// The password is encoded using an unknown scheme. Remove it
// from the user's entry.
deleteValues.add(v);
}
else
{
if (scheme.passwordMatches(oldPassword,
ByteString.valueOf(components[1])))
{
deleteValues.add(v);
}
}
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
// We couldn't decode the provided password value, so remove it
// from the user's entry.
deleteValues.add(v);
}
}
}
AttributeBuilder builder = new AttributeBuilder(attrType);
builder.addAll(deleteValues);
Attribute deleteAttr = builder.toAttribute();
modList.add(new Modification(ModificationType.DELETE, deleteAttr));
// Add the new encoded values.
LinkedHashSet<AttributeValue> addValues =
new LinkedHashSet<AttributeValue>(encodedPasswords.size());
for (ByteString s : encodedPasswords)
{
addValues.add(AttributeValues.create(attrType, s));
}
builder = new AttributeBuilder(attrType);
builder.addAll(addValues);
Attribute addAttr = builder.toAttribute();
modList.add(new Modification(ModificationType.ADD, addAttr));
}
else
{
LinkedHashSet<AttributeValue> replaceValues =
new LinkedHashSet<AttributeValue>(encodedPasswords.size());
for (ByteString s : encodedPasswords)
{
replaceValues.add(
AttributeValues.create(attrType, s));
}
AttributeBuilder builder = new AttributeBuilder(attrType);
builder.addAll(replaceValues);
Attribute addAttr = builder.toAttribute();
modList.add(new Modification(ModificationType.REPLACE, addAttr));
}
// Update the password changed time for the user entry.
pwPolicyState.setPasswordChangedTime();
// If the password was changed by an end user, then clear any reset flag
// that might exist. If the password was changed by an administrator,
// then see if we need to set the reset flag.
if (selfChange)
{
pwPolicyState.setMustChangePassword(false);
}
else
{
pwPolicyState.setMustChangePassword(
pwPolicyState.getPolicy().forceChangeOnReset());
}
// Clear any record of grace logins, auth failures, and expiration
// warnings.
pwPolicyState.clearFailureLockout();
pwPolicyState.clearGraceLoginTimes();
pwPolicyState.clearWarnedTime();
// If the LDAP no-op control was included in the request, then set the
// appropriate response. Otherwise, process the operation.
if (noOpRequested)
{
operation.appendErrorMessage(WARN_EXTOP_PASSMOD_NOOP.get());
operation.setResultCode(ResultCode.NO_OPERATION);
}
else
{
if (selfChange && (requestorEntry == null))
{
requestorEntry = userEntry;
}
// Get an internal connection and use it to perform the modification.
boolean isRoot = DirectoryServer.isRootDN(requestorEntry.getDN());
AuthenticationInfo authInfo = new AuthenticationInfo(requestorEntry,
isRoot);
InternalClientConnection internalConnection = new
InternalClientConnection(authInfo);
ModifyOperation modifyOperation =
internalConnection.processModify(userDN, modList);
ResultCode resultCode = modifyOperation.getResultCode();
if (resultCode != ResultCode.SUCCESS)
{
operation.setResultCode(resultCode);
operation.setErrorMessage(modifyOperation.getErrorMessage());
operation.setReferralURLs(modifyOperation.getReferralURLs());
return;
}
// If there were any password policy state changes, we need to apply
// them using a root connection because the end user may not have
// sufficient access to apply them. This is less efficient than
// doing them all in the same modification, but it's safer.
List<Modification> pwPolicyMods = pwPolicyState.getModifications();
if (! pwPolicyMods.isEmpty())
{
InternalClientConnection rootConnection =
InternalClientConnection.getRootConnection();
ModifyOperation modOp =
rootConnection.processModify(userDN, pwPolicyMods);
if (modOp.getResultCode() != ResultCode.SUCCESS)
{
// At this point, the user's password is already changed so there's
// not much point in returning a non-success result. However, we
// should at least log that something went wrong.
Message message = WARN_EXTOP_PASSMOD_CANNOT_UPDATE_PWP_STATE.get(
String.valueOf(userDN),
String.valueOf(modOp.getResultCode()),
modOp.getErrorMessage());
ErrorLogger.logError(message);
}
}
// If we've gotten here, then everything is OK, so indicate that the
// operation was successful. If a password was generated, then include
// it in the response.
operation.setResultCode(ResultCode.SUCCESS);
if (generatedPassword)
{
ByteStringBuilder builder = new ByteStringBuilder();
ASN1Writer writer = ASN1.getWriter(builder);
try
{
writer.writeStartSequence();
writer.writeOctetString(TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD,
newPassword);
writer.writeEndSequence();
}
catch(Exception e)
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
operation.setResponseValue(builder.toByteString());
}
// If this was a self password change, and the client is authenticated
// as the user whose password was changed, then clear the "must change
// password" flag in the client connection. Note that we're using the
// authentication DN rather than the authorization DN in this case to
// avoid mistakenly clearing the flag for the wrong user.
if (selfChange && (authInfo.getAuthenticationDN() != null) &&
(authInfo.getAuthenticationDN().equals(userDN)))
{
operation.getClientConnection().setMustChangePassword(false);
}
// If the password policy control was requested, then add the
// appropriate response control.
if (pwPolicyRequested)
{
operation.addResponseControl(
new PasswordPolicyResponseControl(pwPolicyWarningType,
pwPolicyWarningValue,
pwPolicyErrorType));
}
}
}