From 11fd34d7f9c620e369fbccaa7c310b5d04a8747e Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Fri, 04 Aug 2006 05:28:15 +0000
Subject: [PATCH] Update the password modify extended operation so that it includes all appropriate password policy processing.
---
opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java | 280 +++++++++++++++++
opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java | 513 ++++++++++++++++++++++++++++----
opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java | 158 +++++++++
3 files changed, 879 insertions(+), 72 deletions(-)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index a164336..d907102 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -180,6 +180,9 @@
// The password policy with which the account is associated.
private PasswordPolicy passwordPolicy;
+ // The string representation of the current time.
+ private String currentGeneralizedTime;
+
// The string representation of the user's DN.
private String userDNString;
@@ -213,6 +216,7 @@
userDNString = userEntry.getDN().toString();
passwordPolicy = getPasswordPolicyInternal();
+ currentGeneralizedTime = TimeThread.getGeneralizedTime();
currentTime = TimeThread.getTime();
modifications = new LinkedList<Modification>();
isDisabled = ConditionResult.UNDEFINED;
@@ -770,6 +774,27 @@
/**
+ * Retrieves the set of values for the password attribute from the user entry.
+ *
+ * @return The set of values for the password attribute from the user entry.
+ */
+ public LinkedHashSet<AttributeValue> getPasswordValues()
+ {
+ assert debugEnter(CLASS_NAME, "getPasswordValues");
+
+ List<Attribute> attrList =
+ userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
+ for (Attribute a : attrList)
+ {
+ return a.getValues();
+ }
+
+ return new LinkedHashSet<AttributeValue>(0);
+ }
+
+
+
+ /**
* Indicates whether the associated password policy requires that
* authentication be performed in a secure manner.
*
@@ -854,6 +879,23 @@
/**
+ * Indicates whether users will be required to provide their current password
+ * when choosing a new one.
+ *
+ * @return <CODE>true</CODE> if users will be required to provide their
+ * current password when choosing a new one, or <CODE>false</CODE>
+ * if not.
+ */
+ public boolean requireCurrentPassword()
+ {
+ assert debugEnter(CLASS_NAME, "requireCurrentPassword");
+
+ return passwordPolicy.requireCurrentPassword();
+ }
+
+
+
+ /**
* Indicates whether administrative password resets should be allowed to
* bypass validity checks for the new password.
*
@@ -937,6 +979,36 @@
/**
+ * Retrieves time that this password policy state object was created.
+ *
+ * @return The time that this password policy state object was created.
+ */
+ public long getCurrentTime()
+ {
+ assert debugEnter(CLASS_NAME, "getCurrentTime");
+
+ return currentTime;
+ }
+
+
+
+ /**
+ * Retrieves the generalized time representation of the time that this
+ * password policy state object was created.
+ *
+ * @return The generalized time representation of the time that this
+ * password policy state object was created.
+ */
+ public String getCurrentGeneralizedTime()
+ {
+ assert debugEnter(CLASS_NAME, "getCurrentGeneralizedTime");
+
+ return currentGeneralizedTime;
+ }
+
+
+
+ /**
* Sets a new value for the password changed time equal to the current time.
*/
public void setPasswordChangedTime()
@@ -1140,7 +1212,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -1384,7 +1456,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -1514,7 +1586,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -1664,7 +1736,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
@@ -1800,7 +1872,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -2336,7 +2408,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -2625,6 +2697,87 @@
/**
+ * Indicates whether users will be allowed to change their passwords if they
+ * are expired.
+ *
+ * @return <CODE>true</CODE> if users will be allowed to change their
+ * passwords if they are expired, or <CODE>false</CODE> if not.
+ */
+ public boolean allowExpiredPasswordChanges()
+ {
+ assert debugEnter(CLASS_NAME, "allowExpiredPasswordChanges");
+
+ return passwordPolicy.allowExpiredPasswordChanges();
+ }
+
+
+
+ /**
+ * Indicates whether the user's last password change was within the minimum
+ * password age.
+ *
+ * @return <CODE>true</CODE> if the password minimum age is nonzero, the
+ * account is not in force-change mode, and the last password change
+ * was within the minimum age, or <CODE>false</CODE> otherwise.
+ */
+ public boolean isWithinMinimumAge()
+ {
+ assert debugEnter(CLASS_NAME, "isWithinMinimumAge");
+
+ int minAge = passwordPolicy.getMinimumPasswordAge();
+ if (minAge <= 0)
+ {
+ // There is no minimum age, so the user isn't in it.
+ if (debug)
+ {
+ debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+ CLASS_NAME, "isWithinMinimumAge",
+ "Returning false because there is no minimum age.");
+ }
+
+ return false;
+ }
+ else if ((passwordChangedTime + (minAge*1000)) < currentTime)
+ {
+ // It's been long enough since the user changed their password.
+ if (debug)
+ {
+ debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+ CLASS_NAME, "isWithinMinimumAge",
+ "Returning false because the minimum age has expired.");
+ }
+
+ return false;
+ }
+ else if (mustChangePassword())
+ {
+ // The user is in a must-change mode, so the minimum age doesn't apply.
+ if (debug)
+ {
+ debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+ CLASS_NAME, "isWithinMinimumAge",
+ "Returning false because the account is in a " +
+ "must-change state.");
+ }
+
+ return false;
+ }
+ else
+ {
+ // The user is within the minimum age.
+ if (debug)
+ {
+ debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.WARNING,
+ CLASS_NAME, "isWithinMinimumAge", "Returning true.");
+ }
+
+ return true;
+ }
+ }
+
+
+
+ /**
* Indicates whether the user may use a grace login if the password is expired
* and there is at least one grace login remaining. Note that this does not
* check to see if the user's password is expired, does not verify that there
@@ -2949,6 +3102,35 @@
/**
+ * Updates the user entry to clear the warned time.
+ */
+ public void clearWarnedTime()
+ {
+ assert debugEnter(CLASS_NAME, "clearWarnedTime");
+
+ AttributeType type =
+ DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
+ if (updateEntry)
+ {
+ userEntry.removeAttribute(type);
+ }
+ else
+ {
+ Attribute a = new Attribute(type);
+ modifications.add(new Modification(ModificationType.REPLACE, a));
+ }
+
+ if (debug)
+ {
+ debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+ CLASS_NAME, "clearWarnedTime",
+ "Cleared the warned time for user " + userDNString);
+ }
+ }
+
+
+
+ /**
* Retrieves the maximum number of grace logins that the user will be allowed
* according to the associated password policy.
*
@@ -3011,7 +3193,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -3159,7 +3341,7 @@
}
else
{
- modifications.add(new Modification(ModificationType.DELETE,
+ modifications.add(new Modification(ModificationType.REPLACE,
new Attribute(type)));
}
}
@@ -3336,6 +3518,88 @@
/**
+ * Indicates whether the user's password is stored using the auth password
+ * syntax or the user password syntax.
+ *
+ * @return <CODE>true</CODE> if the user's password is stored using the auth
+ * password syntax, or <CODE>false</CODE> if it is stored using the
+ * user password syntax.
+ */
+ public boolean usesAuthPasswordSyntax()
+ {
+ assert debugEnter(CLASS_NAME, "usesAuthPasswordSyntax");
+
+ return passwordPolicy.usesAuthPasswordSyntax();
+ }
+
+
+
+ /**
+ * Indicates whether the provided password value is pre-encoded.
+ *
+ * @param passwordValue The value for which to make the determination.
+ *
+ * @return <CODE>true</CODE> if the provided password value is pre-encoded,
+ * or <CODE>false</CODE> if it is not.
+ */
+ public boolean passwordIsPreEncoded(ByteString passwordValue)
+ {
+ assert debugEnter(CLASS_NAME, "isPreEncoded", "ByteString");
+
+ if (passwordPolicy.usesAuthPasswordSyntax())
+ {
+ return AuthPasswordSyntax.isEncoded(passwordValue);
+ }
+ else
+ {
+ return UserPasswordSyntax.isEncoded(passwordValue);
+ }
+ }
+
+
+
+ /**
+ * Encodes the provided password using the default storage schemes (using the
+ * appropriate syntax for the password attribute).
+ *
+ * @param password The password to be encoded.
+ *
+ * @return The password encoded using the default schemes.
+ *
+ * @throws DirectoryException If a problem occurs while attempting to encode
+ * the password.
+ */
+ public List<ByteString> encodePassword(ByteString password)
+ throws DirectoryException
+ {
+ assert debugEnter(CLASS_NAME, "encodePassword", "ByteString");
+
+ List<PasswordStorageScheme> schemes =
+ passwordPolicy.getDefaultStorageSchemes();
+ List<ByteString> encodedPasswords =
+ new ArrayList<ByteString>(schemes.size());
+
+ if (passwordPolicy.usesAuthPasswordSyntax())
+ {
+ for (PasswordStorageScheme s : schemes)
+ {
+ encodedPasswords.add(s.encodeAuthPassword(password));
+ }
+ }
+ else
+ {
+ for (PasswordStorageScheme s : schemes)
+ {
+ encodedPasswords.add(s.encodePasswordWithScheme(password));
+ }
+ }
+
+ return encodedPasswords;
+ }
+
+
+
+ /**
* Indicates whether the provided password appears to be acceptable according
* to the password validators.
*
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 2a3632c..4ff6127 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -29,12 +29,14 @@
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryException;
@@ -43,15 +45,16 @@
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
-import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
-import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.schema.AuthPasswordSyntax;
+import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -59,11 +62,13 @@
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
+import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.ExtensionsMessages.*;
@@ -126,12 +131,6 @@
assert debugEnter(CLASS_NAME, "initializeExtendedOperationHandler",
String.valueOf(configEntry));
- // NYI -- parse the config entry for any settings that might be defined
- // This can include:
- // - Whether to require the old password
- // - Whether to allow automatic generation of a new password
- // - The class name for an algorithm to generate new passwords
-
DirectoryServer.registerSupportedExtension(OID_PASSWORD_MODIFY_REQUEST,
this);
}
@@ -163,9 +162,9 @@
// Initialize the variables associated with components that may be included
// in the request.
- ASN1OctetString userIdentity = null;
- ASN1OctetString oldPassword = null;
- ASN1OctetString newPassword = null;
+ ByteString userIdentity = null;
+ ByteString oldPassword = null;
+ ByteString newPassword = null;
// Parse the encoded request, if there is one.
@@ -246,14 +245,6 @@
}
- // If the user is connected over an insecure channel, then determine
- // whether we should attempt to proceed.
- if (! clientConnection.isSecure())
- {
- // NYI
- }
-
-
// Retrieve a write lock on that user's entry.
userDN = requestorDN;
@@ -343,75 +334,467 @@
}
- // At this point, we should have the user entry. If a current password
- // was provided, then validate it. If not, then see if that's OK.
- AttributeType pwType = DirectoryServer.getAttributeType("userpassword");
- if (pwType == null)
+ // At this point, we should have the user entry. Get the associated
+ // password policy.
+ PasswordPolicyState pwPolicyState;
+ try
{
- pwType = DirectoryServer.getDefaultAttributeType("userPassword");
+ pwPolicyState = new PasswordPolicyState(userEntry, false, false);
+ }
+ catch (DirectoryException de)
+ {
+ assert debugException(CLASS_NAME, "processExtendedOperation", de);
+
+ operation.setResultCode(DirectoryServer.getServerErrorResultCode());
+
+ int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY;
+ operation.appendErrorMessage(getMessage(msgID, String.valueOf(userDN),
+ de.getErrorMessage()));
+ return;
}
+
+ // Determine whether the user is changing his own password or if it's an
+ // administrative reset.
+ boolean selfChange = ((userIdentity == null) ||
+ userDN.equals(requestorDN));
+
+
+ // 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)
{
- // NYI -- Confirm that this is allowed.
+ if (selfChange && pwPolicyState.requireCurrentPassword())
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW;
+ operation.appendErrorMessage(getMessage(msgID));
+ return;
+ }
}
else
{
- // FIXME -- Use a more generic check to determine the correct attribute
- List<Attribute> pwAttrList = userEntry.getAttribute(pwType);
- if ((pwAttrList == null) || pwAttrList.isEmpty())
+ if (pwPolicyState.requireSecureAuthentication() &&
+ (! operation.getClientConnection().isSecure()))
{
- // There were no existing passwords, so the validation will fail.
operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
- int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
- operation.appendErrorMessage(getMessage(msgID));
+ int msgID = MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
return;
}
- AttributeValue matchValue = new AttributeValue(pwType, oldPassword);
-
- boolean matchFound = false;
- for (Attribute a : pwAttrList)
+ if (! pwPolicyState.passwordMatches(oldPassword))
{
- if (a.hasValue(matchValue))
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ return;
+ }
+ }
+
+
+ // If it is a self password change and we don't allow that, then reject
+ // the request.
+ if (selfChange && (! pwPolicyState.allowUserPasswordChanges()))
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ return;
+ }
+
+
+ // If we require secure password changes and the connection isn't secure,
+ // then reject the request.
+ if (pwPolicyState.requireSecurePasswordChanges() &&
+ (! operation.getClientConnection().isSecure()))
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ return;
+ }
+
+
+ // If it's a self-change request and the user is within the minimum age,
+ // then reject it.
+ if (selfChange && pwPolicyState.isWithinMinimumAge())
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_IN_MIN_AGE;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_IN_MIN_AGE;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ 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.allowExpiredPasswordChanges())))
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ 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)
{
- matchFound = true;
- break;
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ return;
+ }
+ else
+ {
+ generatedPassword = true;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ assert debugException(CLASS_NAME, "processExtendedOperation", de);
+
+ if (oldPassword == null)
+ {
+ operation.setResultCode(de.getResultCode());
+
+ int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW;
+ operation.appendErrorMessage(getMessage(msgID,
+ de.getErrorMessage()));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW;
+ operation.appendAdditionalLogMessage(getMessage(msgID,
+ de.getErrorMessage()));
+ }
+
+ 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.allowPreEncodedPasswords())
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED;
+ operation.appendErrorMessage(getMessage(msgID));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED;
+ operation.appendAdditionalLogMessage(getMessage(msgID));
+ }
+
+ return;
+ }
+ }
+ else
+ {
+ if (selfChange || (! pwPolicyState.skipValidationForAdministrators()))
+ {
+ StringBuilder invalidReason = new StringBuilder();
+ if (! pwPolicyState.passwordIsAcceptable(operation, userEntry,
+ newPassword,
+ invalidReason))
+ {
+ if (oldPassword == null)
+ {
+ operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW;
+ operation.appendErrorMessage(getMessage(msgID,
+ String.valueOf(invalidReason)));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW;
+ operation.appendAdditionalLogMessage(getMessage(msgID,
+ String.valueOf(invalidReason)));
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
+
+ // 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)
+ {
+ assert debugException(CLASS_NAME, "processExtendedOperation", de);
+
+ if (oldPassword == null)
+ {
+ operation.setResultCode(de.getResultCode());
+
+ int msgID = MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD;
+ operation.appendErrorMessage(getMessage(msgID,
+ de.getErrorMessage()));
+ }
+ else
+ {
+ operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD;
+ operation.appendAdditionalLogMessage(getMessage(msgID,
+ de.getErrorMessage()));
+ }
+
+ 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.getPasswordAttribute();
+ List<Modification> modList = new ArrayList<Modification>();
+ if (oldPassword != null)
+ {
+ // Remove all existing encoded values that match the old password.
+ LinkedHashSet<AttributeValue> existingValues =
+ pwPolicyState.getPasswordValues();
+ LinkedHashSet<AttributeValue> deleteValues =
+ new LinkedHashSet<AttributeValue>(existingValues.size());
+ if (pwPolicyState.usesAuthPasswordSyntax())
+ {
+ for (AttributeValue v : existingValues)
+ {
+ try
+ {
+ StringBuilder[] components =
+ AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
+ 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)
+ {
+ assert debugException(CLASS_NAME, "processExtendedOperation", 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.getStringValue());
+ PasswordStorageScheme scheme =
+ DirectoryServer.getPasswordStorageScheme(
+ toLowerCase(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.passwordMatches(oldPassword,
+ new ASN1OctetString(components[1])))
+ {
+ deleteValues.add(v);
+ }
+ }
+ }
+ catch (DirectoryException de)
+ {
+ assert debugException(CLASS_NAME, "processExtendedOperation", de);
+
+ // We couldn't decode the provided password value, so remove it
+ // from the user's entry.
+ deleteValues.add(v);
+ }
}
}
- if (! matchFound)
+ Attribute deleteAttr = new Attribute(attrType, attrType.getNameOrOID(),
+ deleteValues);
+ modList.add(new Modification(ModificationType.DELETE, deleteAttr));
+
+
+ // Add the new encoded values.
+ LinkedHashSet<AttributeValue> addValues =
+ new LinkedHashSet<AttributeValue>(encodedPasswords.size());
+ for (ByteString s : encodedPasswords)
{
- // None of the password values matched what the user provided.
- operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
- int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
- operation.appendErrorMessage(getMessage(msgID));
- return;
+ addValues.add(new AttributeValue(attrType, s));
}
+
+ Attribute addAttr = new Attribute(attrType, attrType.getNameOrOID(),
+ addValues);
+ modList.add(new Modification(ModificationType.ADD, addAttr));
}
-
-
- // See if a new password was provided. If not, then generate one.
- boolean generatedPassword = false;
- if (newPassword == null)
+ else
{
- // FIXME -- use an extensible algorithm for generating the new password.
- newPassword = new ASN1OctetString("newpassword");
- generatedPassword = true;
+ LinkedHashSet<AttributeValue> replaceValues =
+ new LinkedHashSet<AttributeValue>(encodedPasswords.size());
+ for (ByteString s : encodedPasswords)
+ {
+ replaceValues.add(new AttributeValue(attrType, s));
+ }
+
+ Attribute addAttr = new Attribute(attrType, attrType.getNameOrOID(),
+ replaceValues);
+ modList.add(new Modification(ModificationType.REPLACE, addAttr));
}
- ArrayList<ASN1OctetString> newPWValues =
- new ArrayList<ASN1OctetString>(1);
- newPWValues.add(newPassword);
+
+ // 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.forceChangeOnReset());
+ }
- // Create the modification to update the user's password.
- LDAPAttribute pwAttr = new LDAPAttribute("userPassword", newPWValues);
- ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>(1);
- mods.add(new LDAPModification(ModificationType.REPLACE, pwAttr));
+
+ // Clear any record of grace logins, auth failures, and expiration
+ // warnings.
+ pwPolicyState.clearAuthFailureTimes();
+ pwPolicyState.clearFailureLockout();
+ pwPolicyState.clearGraceLoginTimes();
+ pwPolicyState.clearWarnedTime();
+
+
+ // Get the list of modifications from the password policy state and add
+ // them to the existing password modifications.
+ modList.addAll(pwPolicyState.getModifications());
// Get an internal connection and use it to perform the modification.
@@ -421,8 +804,7 @@
InternalClientConnection(authInfo);
ModifyOperation modifyOperation =
- internalConnection.processModify(
- new ASN1OctetString(userDN.toString()), mods);
+ internalConnection.processModify(userDN, modList);
ResultCode resultCode = modifyOperation.getResultCode();
if (resultCode != resultCode.SUCCESS)
{
@@ -441,8 +823,11 @@
if (generatedPassword)
{
ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(1);
- newPassword.setType(TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD);
- valueElements.add(newPassword);
+
+ ASN1OctetString newPWString =
+ new ASN1OctetString(TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD,
+ newPassword.value());
+ valueElements.add(newPWString);
ASN1Sequence valueSequence = new ASN1Sequence(valueElements);
operation.setResponseValue(new ASN1OctetString(valueSequence.encode()));
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index 7ae8714..102b377 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -3744,6 +3744,128 @@
/**
+ * The message ID for the message that will be used if an error occurs while
+ * attempting to retrieve the password policy for the user. This takes two
+ * arguments, which are the user DN and a message explaining the problem that
+ * occurred.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 354;
+
+
+
+ /**
+ * The message ID for the message that will be used if a user password change
+ * is rejected because the current password was not provided. This does not
+ * take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 355;
+
+
+
+ /**
+ * The message ID for the message that will be used if a user password change
+ * is rejected because the current password was provided over an insecure
+ * communication channel. This does not take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 356;
+
+
+
+ /**
+ * The message ID for the message that will be used if a user password change
+ * is rejected because users are not allowed to change their passwords. This
+ * does not take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 357;
+
+
+
+ /**
+ * The message ID for the message that will be used if a password change is
+ * rejected because the new password was provided over an insecure
+ * communication channel. This does not take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 358;
+
+
+
+ /**
+ * The message ID for the message that will be used if a user password change
+ * is rejected because the current password is too young. This does not take
+ * any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_IN_MIN_AGE =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 359;
+
+
+
+ /**
+ * The message ID for the message that will be used if a user password change
+ * is rejected because the current password is expired. This does not take
+ * any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 360;
+
+
+
+ /**
+ * The message ID for the message that will be used if a password change is
+ * rejected because no new password was given and there is no password
+ * generator defined. This does not take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 361;
+
+
+
+ /**
+ * The message ID for the message that will be used if an error occurs while
+ * trying to use the password generator to create a new password. This takes
+ * a single argument, which is a message explaining the problem that occurred.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 362;
+
+
+
+ /**
+ * The message ID for the message that will be used if a password change is
+ * rejected because the new password provided was pre-encoded. This does not
+ * take any arguments.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 363;
+
+
+
+ /**
+ * The message ID for the message that will be used if a password change is
+ * rejected because the new password was rejected by a password validator.
+ * This takes a single argument, which is a message explaining the rejection.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 364;
+
+
+
+ /**
+ * The message ID for the message that will be used if a password change is
+ * rejected because the new password could not be encoded using the default
+ * schemes. This takes a single argument, which is a message explaining the
+ * problem that occurred.
+ */
+ public static final int MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 365;
+
+
+
+ /**
* Associates a set of generic messages with the message IDs defined in this
* class.
*/
@@ -3969,6 +4091,42 @@
"The password modify extended operation cannot be " +
"processed because the current password provided for the " +
"use is invalid.");
+ registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY,
+ "An error occurred while attempting to get the " +
+ "password policy for user %s: %s.");
+ registerMessage(MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW,
+ "The current password must be provided for self password " +
+ "changes.");
+ registerMessage(MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED,
+ "Password modify operations that supply the user's " +
+ "current password must be performed over a secure " +
+ "communication channel.");
+ registerMessage(MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED,
+ "End users are not allowed to change their passwords.");
+ registerMessage(MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED,
+ "Password changes must be performed over a secure " +
+ "communication channel.");
+ registerMessage(MSGID_EXTOP_PASSMOD_IN_MIN_AGE,
+ "The password cannot be changed because the previous " +
+ "password change was too recent.");
+ registerMessage(MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED,
+ "The password cannot be changed because it is expired.");
+ registerMessage(MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR,
+ "No new password was provided, and no password generator " +
+ "has been defined that may be used to automatically " +
+ "create a new password.");
+ registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW,
+ "An error occurred while attempting to create a new " +
+ "password using the password generator: %s.");
+ registerMessage(MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED,
+ "The password policy does not allow users to supply " +
+ "pre-encoded passwords.");
+ registerMessage(MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW,
+ "The provided new password failed the validation checks " +
+ "defined in the server: %s.");
+ registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD,
+ "Unable to encode the provided password using the " +
+ "default scheme(s): %s.");
registerMessage(MSGID_NULL_KEYMANAGER_NO_MANAGER,
--
Gitblit v1.10.0