| | |
| | | ds-cfg-minimum-password-length: 6 |
| | | ds-cfg-maximum-password-length: 0 |
| | | |
| | | dn: cn=Similarity-Based Password Validator,cn=Password Validators,cn=config |
| | | objectClass: top |
| | | objectClass: ds-cfg-password-validator |
| | | objectClass: ds-cfg-similarity-based-password-validator |
| | | cn: Similarity-Based Password Validator |
| | | ds-cfg-password-validator-class: org.opends.server.extensions.SimilarityBasedPasswordValidator |
| | | ds-cfg-password-validator-enabled: true |
| | | ds-cfg-minimum-password-difference: 3 |
| | | |
| | | dn: cn=Plugins,cn=config |
| | | objectClass: top |
| | | objectClass: ds-cfg-branch |
| | |
| | | NAME 'ds-cfg-changelog-purge-delay' |
| | | SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE |
| | | X-ORIGIN 'OpenDS Directory Server' ) |
| | | attributeTypes: ( 1.3.6.1.4.1.26027.1.1.321 |
| | | NAME 'ds-cfg-minimum-password-difference' |
| | | SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE |
| | | X-ORIGIN 'OpenDS Directory Server' ) |
| | | objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 |
| | | NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL |
| | | MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled ) |
| | |
| | | STRUCTURAL MUST ( ds-cfg-certificate-fingerprint-attribute-type $ |
| | | ds-cfg-certificate-fingerprint-algorithm ) |
| | | MAY ds-cfg-certificate-user-base-dn X-ORIGIN 'OpenDS Directory Server' ) |
| | | objectClasses: ( 1.3.6.1.4.1.26027.1.2.88 |
| | | NAME 'ds-cfg-similarity-based-password-validator' |
| | | SUP ds-cfg-password-validator STRUCTURAL |
| | | MUST ds-cfg-minimum-password-difference X-ORIGIN 'OpenDS Directory Server' ) |
| | | |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <adm:managed-object name="similarity-based-password-validator" |
| | | plural-name="similarity-based-password-validators" |
| | | package="org.opends.server.admin.std" extends="password-validator" |
| | | xmlns:adm="http://www.opends.org/admin" |
| | | xmlns:ldap="http://www.opends.org/admin-ldap"> |
| | | <adm:synopsis> |
| | | The |
| | | <adm:user-friendly-name /> |
| | | is used to determine whether a proposed password is acceptable based on |
| | | whether the number of characters it contains falls within an acceptable |
| | | range of values. |
| | | </adm:synopsis> |
| | | <adm:profile name="ldap"> |
| | | <ldap:object-class> |
| | | <ldap:oid>1.3.6.1.4.1.26027.1.2.88</ldap:oid> |
| | | <ldap:name>ds-cfg-similarity-based-password-validator</ldap:name> |
| | | <ldap:superior>ds-cfg-password-validator</ldap:superior> |
| | | </ldap:object-class> |
| | | </adm:profile> |
| | | <adm:property name="minimum-password-difference" mandatory="true"> |
| | | <adm:synopsis> |
| | | Specifies the minimum difference of new and old password. |
| | | </adm:synopsis> |
| | | <adm:description> |
| | | Specifies the minimal difference of new and old password. |
| | | A value of zero indicates that there will be no difference is acceptable. |
| | | Changes to this configuration attribute will take effect immediately. |
| | | </adm:description> |
| | | <adm:default-behavior> |
| | | <adm:defined> |
| | | <adm:value>1</adm:value> |
| | | </adm:defined> |
| | | </adm:default-behavior> |
| | | <adm:syntax> |
| | | <adm:integer lower-limit="0" /> |
| | | </adm:syntax> |
| | | <adm:profile name="ldap"> |
| | | <ldap:attribute> |
| | | <ldap:oid>1.3.6.1.4.1.26027.1.1.321</ldap:oid> |
| | | <ldap:name>ds-cfg-minimum-password-difference</ldap:name> |
| | | </ldap:attribute> |
| | | </adm:profile> |
| | | </adm:property> |
| | | </adm:managed-object> |
| | | |
| | |
| | | public static final String ATTR_PASSWORD_MIN_LENGTH = |
| | | NAME_PREFIX_CFG + "minimum-password-length"; |
| | | |
| | | /** |
| | | * The name of the configuration attribute that specifies the minimum allowed |
| | | * difference for a password. |
| | | */ |
| | | public static final String ATTR_PASSWORD_MIN_DIFFERENCE = |
| | | NAME_PREFIX_CFG + "minimum-password-difference"; |
| | | |
| | | |
| | | /** |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE |
| | | * or https://OpenDS.dev.java.net/OpenDS.LICENSE. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2007 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.opends.server.api.PasswordValidator; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.core.Operation; |
| | | import org.opends.server.types.ByteString; |
| | | import org.opends.server.types.ByteStringFactory; |
| | | import org.opends.server.types.ConfigChangeResult; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.util.LevenshteinDistance; |
| | | import org.opends.server.admin.std.server.SimilarityBasedPasswordValidatorCfg; |
| | | import org.opends.server.admin.server.ConfigurationChangeListener; |
| | | |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.loggers.Error.*; |
| | | import static org.opends.server.messages.ExtensionsMessages.*; |
| | | import static org.opends.server.messages.MessageHandler.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class provides a password validator that can ensure that the provided |
| | | * password meets minimum similarity requirements. |
| | | */ |
| | | public class SimilarityBasedPasswordValidator extends |
| | | PasswordValidator<SimilarityBasedPasswordValidatorCfg> implements |
| | | ConfigurationChangeListener<SimilarityBasedPasswordValidatorCfg> |
| | | { |
| | | |
| | | // The current configuration for this password validator. |
| | | private SimilarityBasedPasswordValidatorCfg currentConfig; |
| | | |
| | | |
| | | /** |
| | | * Creates a new instance of this password validator. |
| | | */ |
| | | public SimilarityBasedPasswordValidator() |
| | | { |
| | | super(); |
| | | |
| | | |
| | | // All initialization must be done in the initializePasswordValidator |
| | | // method. |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public void initializePasswordValidator( |
| | | SimilarityBasedPasswordValidatorCfg configuration) |
| | | throws ConfigException, InitializationException |
| | | { |
| | | configuration.addSimilarityBasedChangeListener(this); |
| | | |
| | | currentConfig = configuration; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public void finalizePasswordValidator() |
| | | { |
| | | currentConfig.removeSimilarityBasedChangeListener(this); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public boolean passwordIsAcceptable(ByteString newPassword, |
| | | Set<ByteString> currentPasswords, |
| | | Operation operation, Entry userEntry, |
| | | StringBuilder invalidReason) { |
| | | |
| | | int minDifference = currentConfig.getMinimumPasswordDifference(); |
| | | ByteString passwd = newPassword == null |
| | | ? ByteStringFactory.create("") |
| | | : newPassword; |
| | | |
| | | if (currentPasswords == null || currentPasswords.size() == 0) { |
| | | // This validator requires access to at least one current password. |
| | | // If we don't have a current password, then we can't validate it, so |
| | | // we'll have to assume it is OK. Ideally, the password policy should be |
| | | // configured to always require the current password, but even then the |
| | | // current password probably won't be availble during an administrative |
| | | // password reset. |
| | | return true; |
| | | } |
| | | |
| | | for (ByteString bs : currentPasswords) { |
| | | if (bs == null) { |
| | | continue; |
| | | } |
| | | int ldistance = LevenshteinDistance.calculate(passwd.stringValue(), |
| | | bs.stringValue()); |
| | | if (ldistance < minDifference) { |
| | | invalidReason.append(getMessage(MSGID_PWDIFFERENCEVALIDATOR_TOO_SMALL, |
| | | minDifference)); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationChangeAcceptable( |
| | | SimilarityBasedPasswordValidatorCfg configuration, |
| | | List<String> unacceptableReasons) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | SimilarityBasedPasswordValidatorCfg configuration) |
| | | { |
| | | currentConfig = configuration; |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | } |
| | | |
| | |
| | | CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 452; |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used as the description of the |
| | | * minimum difference configuration attribute. It does not take any |
| | | * arguments. |
| | | */ |
| | | public static final int |
| | | MSGID_PWDIFFERENCEVALIDATOR_DESCRIPTION_MIN_DIFFERENCE = |
| | | CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 453; |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used if an error occurs while |
| | | * attempting to determine the minimum password difference. This takes a |
| | | * single argument, which is a string representation of the exception that was |
| | | * caught. |
| | | */ |
| | | public static final int |
| | | MSGID_PWDIFFERENCEVALIDATOR_CANNOT_DETERMINE_MIN_DIFFERENCE = |
| | | CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 454; |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used to indicate that the |
| | | * minimum password length has been updated. This takes a single argument, |
| | | * which is the new minimum length. |
| | | */ |
| | | public static final int MSGID_PWDIFFERENCEVALIDATOR_UPDATED_MIN_DIFFERENCE = |
| | | CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 455; |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used if a provided password is |
| | | * too short. This takes a single argument, which is the minimum required |
| | | * password length. |
| | | */ |
| | | public static final int MSGID_PWDIFFERENCEVALIDATOR_TOO_SMALL = |
| | | CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 456; |
| | | |
| | | |
| | | /** |
| | | * Associates a set of generic messages with the message IDs defined in this |
| | |
| | | "The minimum password length has been updated to %d."); |
| | | registerMessage(MSGID_PWLENGTHVALIDATOR_UPDATED_MAX_LENGTH, |
| | | "The maximum password length has been updated to %d."); |
| | | registerMessage(MSGID_PWDIFFERENCEVALIDATOR_DESCRIPTION_MIN_DIFFERENCE, |
| | | "Specifies the minimum difference that a " + |
| | | "password will be allowed to have. A value of zero " + |
| | | "indicates that there is no minimum difference. Changes " + |
| | | "to this configuration attribute will take effect " + |
| | | "immediately."); |
| | | registerMessage(MSGID_PWDIFFERENCEVALIDATOR_CANNOT_DETERMINE_MIN_DIFFERENCE, |
| | | "An error occurred while attempting to determine the " + |
| | | "minimum allowed password difference from the " + |
| | | ATTR_PASSWORD_MIN_DIFFERENCE + " attribute: %s."); |
| | | registerMessage(MSGID_PWDIFFERENCEVALIDATOR_TOO_SMALL, |
| | | "The provided password differs less than the minimum " + |
| | | "required difference of %d characters."); |
| | | registerMessage(MSGID_PWDIFFERENCEVALIDATOR_UPDATED_MIN_DIFFERENCE, |
| | | "The minimum password difference has been updated to %d."); |
| | | |
| | | |
| | | registerMessage(MSGID_RANDOMPWGEN_DESCRIPTION_CHARSET, |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE |
| | | * or https://OpenDS.dev.java.net/OpenDS.LICENSE. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2007 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.util; |
| | | |
| | | |
| | | |
| | | import static org.opends.server.util.Validator.*; |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class provides an implementation of the Levenshtein distance algorithm, |
| | | * which may be used to determine the minimum number of changes required to |
| | | * transform one string into another. For the purpose of this algorithm, a |
| | | * change is defined as replacing one character with another, inserting a new |
| | | * character, or removing an existing character. |
| | | * <BR><BR> |
| | | * The basic algorithm works as follows for a source string S of length X and |
| | | * a target string T of length Y: |
| | | * <OL> |
| | | * <LI>Create a matrix M with dimensions of X+1, Y+1.</LI> |
| | | * <LI>Fill the first row with sequentially-increasing values 0 through |
| | | * X.</LI> |
| | | * <LI>Fill the first column with sequentially-increasing values 0 through |
| | | * Y.</LI> |
| | | * <LI>Create a nested loop iterating over the characters in the strings. In |
| | | * the outer loop, iterate through characters in S from 0 to X-1 using an |
| | | * iteration counter I. In the inner loop, iterate through the characters |
| | | * in T from 0 to Y-1 using an iterator counter J. Calculate the |
| | | * following three things and place the smallest value in the matrix in |
| | | * row I+1 column J+1: |
| | | * <UL> |
| | | * <LI>One more than the value in the matrix position immediately to the |
| | | * left (i.e., 1 + M[I][J+1]).</LI> |
| | | * <LI>One more than the value in the matrix position immediately above |
| | | * (i.e., 1 + M[I+1][J]).</LI> |
| | | * <LI>Define a value V to be zero if S[I] is the same as T[J], or one if |
| | | * they are different. Add that value to the value in the matrix |
| | | * position immediately above and to the left (i.e., |
| | | * V + M[I][J]).</LI> |
| | | * </UL> |
| | | * </LI> |
| | | * <LI>The Levenshtein distance value, which is the least number of changes |
| | | * needed to transform the source string into the target string, will be |
| | | * the value in the bottom right corner of the matrix (i.e., |
| | | * M[X][Y]).</LI> |
| | | * </OL> |
| | | * <BR><BR> |
| | | * Note that this is a completely "clean room" implementation, developed from a |
| | | * description of the algorithm, rather than copying an existing implementation. |
| | | * Doing it in this way eliminates copyright and licensing concerns associated |
| | | * with using an existing implementation. |
| | | */ |
| | | public final class LevenshteinDistance |
| | | { |
| | | /** |
| | | * Calculates the Levenshtein distance between the provided string values. |
| | | * |
| | | * @param source The source string to compare. It must not be {@code null}. |
| | | * @param target The target string to compare. It must not be {@code null}. |
| | | * |
| | | * @return The minimum number of changes required to turn the source string |
| | | * into the target string. |
| | | */ |
| | | public static int calculate(String source, String target) |
| | | { |
| | | ensureNotNull(source, target); |
| | | |
| | | // sl == source length; tl == target length |
| | | int sl = source.length(); |
| | | int tl = target.length(); |
| | | |
| | | |
| | | // If either of the lengths is zero, then the distance is the length of the |
| | | // other string. |
| | | if (sl == 0) |
| | | { |
| | | return tl; |
| | | } |
| | | else if (tl == 0) |
| | | { |
| | | return sl; |
| | | } |
| | | |
| | | |
| | | // w == matrix width; h == matrix height |
| | | int w = sl+1; |
| | | int h = tl+1; |
| | | |
| | | |
| | | // m == matrix array |
| | | // Create the array and fill it with values 0..sl in the first dimension and |
| | | // 0..tl in the second dimension. |
| | | int[][] m = new int[w][h]; |
| | | for (int i=0; i < w; i++) |
| | | { |
| | | m[i][0] = i; |
| | | } |
| | | |
| | | for (int i=1; i < h; i++) |
| | | { |
| | | m[0][i] = i; |
| | | } |
| | | |
| | | for (int i=0,x=1; i < sl; i++,x++) |
| | | { |
| | | char s = source.charAt(i); |
| | | |
| | | for (int j=0,y=1; j < tl; j++,y++) |
| | | { |
| | | char t = target.charAt(j); |
| | | |
| | | |
| | | // Figure out what to put in the appropriate matrix slot. It should be |
| | | // the lowest of: |
| | | // - One more than the value to the left |
| | | // - One more than the value to the top |
| | | // - If the characters are equal, the value to the upper left, otherwise |
| | | // one more than the value to the upper left. |
| | | m[x][y] = Math.min(Math.min((m[i][y] + 1), (m[x][j] + 1)), |
| | | (m[i][j] + ((s == t) ? 0 : 1))); |
| | | } |
| | | } |
| | | |
| | | // The Levenshtein distance should now be the value in the lower right |
| | | // corner of the matrix. |
| | | return m[sl][tl]; |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Retrieves a set of invvalid configuration entries. |
| | | * Retrieves a set of invalid configuration entries. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE |
| | | * or https://OpenDS.dev.java.net/OpenDS.LICENSE. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2006-2007 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.ByteString; |
| | | import org.opends.server.types.ByteStringFactory; |
| | | import org.opends.server.types.Control; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.ModificationType; |
| | | import static org.testng.Assert.*; |
| | | import org.opends.server.admin.std.meta.SimilarityBasedPasswordValidatorCfgDefn; |
| | | import org.opends.server.admin.std.server.SimilarityBasedPasswordValidatorCfg; |
| | | import org.opends.server.admin.server.AdminTestCaseUtils; |
| | | |
| | | |
| | | |
| | | /** |
| | | * A set of test cases for the Similarity-Based Password Validator. |
| | | */ |
| | | public class SimilarityBasedPasswordValidatorTestCase |
| | | extends ExtensionsTestCase |
| | | { |
| | | /** |
| | | * Ensures that the Directory Server is running. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @BeforeClass() |
| | | public void startServer() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.startServer(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves a set of valid configuration entries that may be used to |
| | | * initialize the validator. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @DataProvider(name = "validConfigs") |
| | | public Object[][] getValidConfigs() |
| | | throws Exception |
| | | { |
| | | List<Entry> entries = TestCaseUtils.makeEntries( |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: 6", |
| | | "", |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: 3", |
| | | "", |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: 0" |
| | | ); |
| | | |
| | | Object[][] array = new Object[entries.size()][1]; |
| | | for (int i=0; i < array.length; i++) |
| | | { |
| | | array[i] = new Object[] { entries.get(i) }; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the process of initializing the server with valid configurations. |
| | | * |
| | | * @param entry The configuration entry to use for the initialization. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test(dataProvider = "validConfigs") |
| | | public void testInitializeWithValidConfigs(Entry e) |
| | | throws Exception |
| | | { |
| | | SimilarityBasedPasswordValidatorCfg configuration = |
| | | AdminTestCaseUtils.getConfiguration( |
| | | SimilarityBasedPasswordValidatorCfgDefn.getInstance(), |
| | | e); |
| | | |
| | | SimilarityBasedPasswordValidator validator = new SimilarityBasedPasswordValidator(); |
| | | validator.initializePasswordValidator(configuration); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves a set of invvalid configuration entries. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @DataProvider(name = "invalidConfigs") |
| | | public Object[][] getInvalidConfigs() |
| | | throws Exception |
| | | { |
| | | List<Entry> entries = TestCaseUtils.makeEntries( |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: -1", |
| | | "", |
| | | |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | // "ds-cfg-minimum-password-difference: -1", // error here |
| | | "", |
| | | |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: notNumeric"); |
| | | |
| | | Object[][] array = new Object[entries.size()][1]; |
| | | for (int i=0; i < array.length; i++) |
| | | { |
| | | array[i] = new Object[] { entries.get(i) }; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the process of initializing the server with invalid configurations. |
| | | * |
| | | * @param entry The configuration entry to use for the initialization. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test(dataProvider = "invalidConfigs", |
| | | expectedExceptions = { ConfigException.class, |
| | | InitializationException.class }) |
| | | public void testInitializeWithInvalidConfigs(Entry e) |
| | | throws Exception |
| | | { |
| | | SimilarityBasedPasswordValidatorCfg configuration = |
| | | AdminTestCaseUtils.getConfiguration( |
| | | SimilarityBasedPasswordValidatorCfgDefn.getInstance(), |
| | | e); |
| | | |
| | | SimilarityBasedPasswordValidator validator = new SimilarityBasedPasswordValidator(); |
| | | validator.initializePasswordValidator(configuration); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the <CODE>passwordIsAcceptable</CODE> method with no constraints on |
| | | * password difference. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testPasswordIsAcceptableNoConstraints() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | Entry userEntry = TestCaseUtils.makeEntry( |
| | | "dn: uid=test.user,o=test", |
| | | "objectClass: top", |
| | | "objectClass: person", |
| | | "objectClass: organizationalPerson", |
| | | "objectClass: inetOrgPerson", |
| | | "uid: test.user", |
| | | "givenName: Test", |
| | | "sn: User", |
| | | "cn: Test User", |
| | | "userPassword: password"); |
| | | |
| | | Entry validatorEntry = TestCaseUtils.makeEntry( |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: 0" |
| | | ); |
| | | |
| | | SimilarityBasedPasswordValidatorCfg configuration = |
| | | AdminTestCaseUtils.getConfiguration( |
| | | SimilarityBasedPasswordValidatorCfgDefn.getInstance(), |
| | | validatorEntry); |
| | | |
| | | SimilarityBasedPasswordValidator validator = |
| | | new SimilarityBasedPasswordValidator(); |
| | | validator.initializePasswordValidator(configuration); |
| | | |
| | | StringBuilder buffer = new StringBuilder(); |
| | | for (int i=0; i < 20; i++) |
| | | { |
| | | buffer.append('x'); |
| | | ASN1OctetString password = new ASN1OctetString(buffer.toString()); |
| | | |
| | | ArrayList<Modification> mods = new ArrayList<Modification>(); |
| | | mods.add(new Modification(ModificationType.REPLACE, |
| | | new Attribute("userpassword", |
| | | buffer.toString()))); |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | ModifyOperation op = |
| | | new ModifyOperation(conn, conn.nextOperationID(), |
| | | conn.nextMessageID(), new ArrayList<Control>(), |
| | | DN.decode("cn=uid=test.user,o=test"), mods); |
| | | |
| | | StringBuilder invalidReason = new StringBuilder(); |
| | | assertTrue(validator.passwordIsAcceptable(password, |
| | | new HashSet<ByteString>(0), |
| | | op, userEntry, invalidReason)); |
| | | } |
| | | |
| | | validator.finalizePasswordValidator(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the <CODE>passwordIsAcceptable</CODE> method with a constraint on the |
| | | * minimum password difference. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testPasswordIsAcceptableMinDifferenceConstraint() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | Entry userEntry = TestCaseUtils.makeEntry( |
| | | "dn: uid=test.user,o=test", |
| | | "objectClass: top", |
| | | "objectClass: person", |
| | | "objectClass: organizationalPerson", |
| | | "objectClass: inetOrgPerson", |
| | | "uid: test.user", |
| | | "givenName: Test", |
| | | "sn: User", |
| | | "cn: Test User", |
| | | "userPassword: password"); |
| | | |
| | | Entry validatorEntry = TestCaseUtils.makeEntry( |
| | | "dn: cn=Similarity-Based Password Validator,cn=Password Validators," + |
| | | "cn=config", |
| | | "objectClass: top", |
| | | "objectClass: ds-cfg-password-validator", |
| | | "objectClass: ds-cfg-similarity-based-password-validator", |
| | | "cn: Similarity-Based Password Validator", |
| | | "ds-cfg-password-validator-class: org.opends.server.extensions." + |
| | | "SimilarityBasedPasswordValidator", |
| | | "ds-cfg-password-validator-enabled: true", |
| | | "ds-cfg-minimum-password-difference: 3" |
| | | ); |
| | | |
| | | SimilarityBasedPasswordValidatorCfg configuration = |
| | | AdminTestCaseUtils.getConfiguration( |
| | | SimilarityBasedPasswordValidatorCfgDefn.getInstance(), |
| | | validatorEntry); |
| | | |
| | | SimilarityBasedPasswordValidator validator = |
| | | new SimilarityBasedPasswordValidator(); |
| | | validator.initializePasswordValidator(configuration); |
| | | |
| | | StringBuilder buffer = new StringBuilder(); |
| | | HashSet<ByteString> currentPassword = new HashSet<ByteString>(3); |
| | | currentPassword.add(ByteStringFactory.create("xxx")); |
| | | for (int i=0; i < 7; i++) |
| | | { |
| | | buffer.append('x'); |
| | | ASN1OctetString password = new ASN1OctetString(buffer.toString()); |
| | | |
| | | ArrayList<Modification> mods = new ArrayList<Modification>(); |
| | | mods.add(new Modification(ModificationType.REPLACE, |
| | | new Attribute("userpassword", |
| | | buffer.toString()))); |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | ModifyOperation op = |
| | | new ModifyOperation(conn, conn.nextOperationID(), |
| | | conn.nextMessageID(), new ArrayList<Control>(), |
| | | DN.decode("cn=uid=test.user,o=test"), mods); |
| | | |
| | | StringBuilder invalidReason = new StringBuilder(); |
| | | assertEquals((buffer.length() >= 6), |
| | | validator.passwordIsAcceptable(password, |
| | | currentPassword, |
| | | op, userEntry, |
| | | invalidReason)); |
| | | } |
| | | |
| | | validator.finalizePasswordValidator(); |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE |
| | | * or https://OpenDS.dev.java.net/OpenDS.LICENSE. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2007 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.util; |
| | | |
| | | |
| | | |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import static org.testng.Assert.*; |
| | | |
| | | |
| | | |
| | | /** |
| | | * A set of generic test cases for the Levenshtein distance class. |
| | | */ |
| | | public class LevenshteinDistanceTestCase |
| | | extends UtilTestCase |
| | | { |
| | | /** |
| | | * Retrieves a set of data that may be used to test the Levenshtein distance |
| | | * implementation. Each element of the array returned will itself be an |
| | | * array whose elements are a source string, a target string, and the |
| | | * expected Levenshtein distance. |
| | | * |
| | | * @return A set of data that may be used to test the Levenshtein distance |
| | | * implementation. |
| | | */ |
| | | @DataProvider(name = "teststrings") |
| | | public Object[][] getTestStrings() |
| | | { |
| | | return new Object[][] |
| | | { |
| | | // When the values are the same, the distance is zero. |
| | | new Object[] { "", "", 0 }, |
| | | new Object[] { "1", "1", 0 }, |
| | | new Object[] { "12", "12", 0 }, |
| | | new Object[] { "123", "123", 0 }, |
| | | new Object[] { "1234", "1234", 0 }, |
| | | new Object[] { "12345", "12345", 0 }, |
| | | new Object[] { "password", "password", 0 }, |
| | | |
| | | |
| | | // When one of the values is empty, the distance is the length of the |
| | | // other value. |
| | | new Object[] { "", "1", 1 }, |
| | | new Object[] { "", "12", 2 }, |
| | | new Object[] { "", "123", 3 }, |
| | | new Object[] { "", "1234", 4 }, |
| | | new Object[] { "", "12345", 5 }, |
| | | new Object[] { "", "password", 8 }, |
| | | new Object[] { "1", "", 1 }, |
| | | new Object[] { "12", "", 2 }, |
| | | new Object[] { "123", "", 3 }, |
| | | new Object[] { "1234", "", 4 }, |
| | | new Object[] { "12345", "", 5 }, |
| | | new Object[] { "password", "", 8 }, |
| | | |
| | | |
| | | // Whenever a single character is inserted or removed, the distance is |
| | | // one. |
| | | new Object[] { "password", "1password", 1 }, |
| | | new Object[] { "password", "p1assword", 1 }, |
| | | new Object[] { "password", "pa1ssword", 1 }, |
| | | new Object[] { "password", "pas1sword", 1 }, |
| | | new Object[] { "password", "pass1word", 1 }, |
| | | new Object[] { "password", "passw1ord", 1 }, |
| | | new Object[] { "password", "passwo1rd", 1 }, |
| | | new Object[] { "password", "passwor1d", 1 }, |
| | | new Object[] { "password", "password1", 1 }, |
| | | new Object[] { "password", "assword", 1 }, |
| | | new Object[] { "password", "pssword", 1 }, |
| | | new Object[] { "password", "pasword", 1 }, |
| | | new Object[] { "password", "pasword", 1 }, |
| | | new Object[] { "password", "passord", 1 }, |
| | | new Object[] { "password", "passwrd", 1 }, |
| | | new Object[] { "password", "passwod", 1 }, |
| | | new Object[] { "password", "passwor", 1 }, |
| | | |
| | | |
| | | // Whenever a single character is replaced, the distance is one. |
| | | new Object[] { "password", "Xassword", 1 }, |
| | | new Object[] { "password", "pXssword", 1 }, |
| | | new Object[] { "password", "paXsword", 1 }, |
| | | new Object[] { "password", "pasXword", 1 }, |
| | | new Object[] { "password", "passXord", 1 }, |
| | | new Object[] { "password", "passwXrd", 1 }, |
| | | new Object[] { "password", "passwoXd", 1 }, |
| | | new Object[] { "password", "passworX", 1 }, |
| | | |
| | | |
| | | // If characters are taken off the front and added to the back and all of |
| | | // the characters are unique, then the distance is two times the number of |
| | | // characters shifted, until you get halfway (and then it becomes easier |
| | | // to shift from the other direction). |
| | | new Object[] { "12345678", "23456781", 2 }, |
| | | new Object[] { "12345678", "34567812", 4 }, |
| | | new Object[] { "12345678", "45678123", 6 }, |
| | | new Object[] { "12345678", "56781234", 8 }, |
| | | new Object[] { "12345678", "67812345", 6 }, |
| | | new Object[] { "12345678", "78123456", 4 }, |
| | | new Object[] { "12345678", "81234567", 2 }, |
| | | |
| | | |
| | | // If all the characters are unique and the values are reversed, then the |
| | | // distance is the number of characters for an even number of characters, |
| | | // and one less for an odd number of characters (since the middle |
| | | // character will stay the same). |
| | | new Object[] { "12", "21", 2 }, |
| | | new Object[] { "123", "321", 2 }, |
| | | new Object[] { "1234", "4321", 4 }, |
| | | new Object[] { "12345", "54321", 4 }, |
| | | new Object[] { "123456", "654321", 6 }, |
| | | new Object[] { "1234567", "7654321", 6 }, |
| | | new Object[] { "12345678", "87654321", 8 }, |
| | | |
| | | |
| | | // The rest of these are miscellaneous interesting examples. They will |
| | | // be illustrated using the following key: |
| | | // = (the characters are equal) |
| | | // + (the character is inserted) |
| | | // - (the character is removed) |
| | | // # (the character is replaced) |
| | | |
| | | // Mississippi |
| | | // ippississiM |
| | | // -=##====##=+ --> 6 |
| | | new Object[] { "Mississippi", "ippississiM", 6 }, |
| | | |
| | | // eieio |
| | | // oieie |
| | | // #===# --> 2 |
| | | new Object[] { "eieio", "oieie", 2 }, |
| | | |
| | | // brad+angelina |
| | | // bra ngelina |
| | | // ===+++======= --> 3 |
| | | new Object[] { "brad+angelina", "brangelina", 3 }, |
| | | |
| | | // test international chars |
| | | // ?e?uli?ka |
| | | // e?uli?ka |
| | | // -======== --> 1 |
| | | new Object[] { "?e?uli?ka", "e?uli?ka", 1 }, |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the {@code calculate} method with non-{@code null} String arguments. |
| | | * |
| | | * @param s The source string to compare. |
| | | * @param t The target string to compare. |
| | | * @param d The expected Levenshtein distance for the two strings. |
| | | */ |
| | | @Test(dataProvider = "teststrings") |
| | | public void testCalculateStrings(String s, String t, int d) |
| | | { |
| | | assertEquals(LevenshteinDistance.calculate(s, t), d); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the {@code calculate} method with non-{@code null} String arguments |
| | | * in reverse order to verify that they are order-independent. |
| | | * |
| | | * @param s The source string to compare. |
| | | * @param t The target string to compare. |
| | | * @param d The expected Levenshtein distance for the two strings. |
| | | */ |
| | | @Test(dataProvider = "teststrings") |
| | | public void testCalculateStringsReversed(String s, String t, int d) |
| | | { |
| | | assertEquals(LevenshteinDistance.calculate(t, s), d); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Retrieves a set of data that may be used to test the Levenshtein distance |
| | | * implementation. Each element of the array returned will itself be an |
| | | * array whose elements are a source string, a target string, and the |
| | | * expected Levenshtein distance. |
| | | * |
| | | * @return A set of data that may be used to test the Levenshtein distance |
| | | * implementation. |
| | | */ |
| | | @DataProvider(name = "testnulls") |
| | | public Object[][] getTestNulls() |
| | | { |
| | | return new Object[][] |
| | | { |
| | | new Object[] { "notnull", null }, |
| | | new Object[] { null, "notnull" }, |
| | | new Object[] { null, null } |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the {@code calculate} method with at least one {@code null} string. |
| | | * |
| | | * @param s The source string to compare. |
| | | * @param t The target string to compare. |
| | | */ |
| | | @Test(dataProvider = "testnulls", |
| | | expectedExceptions = { AssertionError.class }) |
| | | public void testNullStrings(String s, String t) |
| | | { |
| | | LevenshteinDistance.calculate(s, t); |
| | | } |
| | | } |
| | | |