Code

Flexible Password Validation Rules

Seane_in_Marachi_Pose_st

Flexible Password Validation Rules
David King

In order to facilitate password validation flexibility, the following design was used.

An external JSON configuration file will be read in at runtime from the external

properties / json files location that resides on each server environment

for the application.

The file, for example PasswordRules.json will contain an object representing

the locale supported and provide a way to match on a given field key

as to provide a way to configure multiple rule sets where each

key is unique.

PasswordRules.json excerpt is shown below:

/*

PasswordRules.json

*/
{
“USA”: {
“Fields”: {
“Field”: {
“name”:”Password”,
“regExActive”:”yes”,
“regEx”:”^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[\\p{Punct}])(?=\\S+$).{8,64}$”,
“minimumNumberOfCharacters”:”0″,
“maximumNumberOfCharacters”:”0″,
“minimumNumberOfDigits”:”0″,
“minimumNumberOfCapitalLetters”:”0″,
“minimumNumberOfSpecialCharacters”:”0″
}
}
},
“CANADA”: {
“Fields”: {
“Field”: {
“name”:”Password”,
“regExActive”:”no”,
“regEx”:”^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[\\p{Punct}])(?=\\S+$).{8,64}$”,
“minimumNumberOfCharacters”:”6″,
“maximumNumberOfCharacters”:”64″,
“minimumNumberOfDigits”:”1″,
“minimumNumberOfCapitalLetters”:”1″,
“minimumNumberOfSpecialCharacters”:”1″
}
}
}
}

The underlying code that processes this JSON configuration uses the following algorithm.

RULES ALGORITHM:
1. Retrieve the locale for the given user and application instance.
2. Determine if the given locale is supported, if not indicate this to the user, otherwise proceed.
3. Retrieve the meta data from the JSON configuration file for the given product using the naming
convention, $ProductOrServiceName + PasswordRules + .json, otherwise indicate support not available
for the given locale and application instance.
4. If regular expressions are not active Then
– Compare the given password value to:
* minimum number of characters required
* maximum number of characters required
* minimum number of digits required
* minimum number of capital letters required
* minimum number of special characters required

Otherwise
– Compare the given password value to the configured regular expression


FlexiblePasswordTest.java

package concepts;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import processor.util.Constants;
import processor.util.JsonUtil;

import java.util.Collections;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static junit.framework.TestCase.assertNotNull;

public class FlexiblePasswordTest {
 JsonUtil jsonUtil;
 Map<String, Object> passwordRules;

@Test
 public void testLoadPasswordRulesJson() {
 assertNotNull("PasswordRules Map was null", passwordRules);
 o(passwordRules.toString());
 }

@Test
 public void testPopulateRulesLocaleAndFieldsMaps() {
 String locale = "canada".toUpperCase();

Map<String, Object> localeMap = (Map<String, Object>) passwordRules.get(locale);
 Map<String, Object> fieldsMap = (Map<String, Object>) localeMap.get(Constants.FIELDS);
 Map<String, Object> fieldMap = (Map<String, Object>) fieldsMap.get(Constants.FIELD);

assertNotNull("Locale map is null", localeMap);
 assertNotNull("Fields map is null", fieldsMap);
 assertNotNull("Field map is null", fieldMap);

o(String.format("%nLOCALE: %s%n", localeMap));
 o(String.format("FIELDS: %s%n", fieldsMap));
 o(String.format("FIELD: %s%n", fieldMap));
 }

@Test
 public void testValidateLocalePasswordNoRegEx() {
// String locale = "canada".toUpperCase();
 String locale = "usa".toUpperCase();

Map<String, Object> passwordMetaData = getPasswordMetaDataForLocale(locale);
 o(String.format("passwordMetaData for %s: %s%n", locale, passwordMetaData));

if (!passwordMetaData.isEmpty()) {

 // simulated arguments from password call
 String key = "Password";
 String val = "Thpwd";
// String val = "wtHis!passwd7890123wtHis!passwd7890123wtHis!passwd7890123wtHis!passwd7890123";
// String val = "wtHis!passwd7890123";
// String val = "wthis!passwd7";
// String val = "WthIs!passwd7";

////////////////////////////////////////////////////////////////////////
 // validate using rules
 //
 // public Return validatePasswordUsingExternalRules(...) {}

// is this the correct field to validate?
 if (key.equalsIgnoreCase(passwordMetaData.get(Constants.FieldName).toString())) {
 // Rules
 // In general, if the rule is not equal to zero, validate.

// is RegEx active?
 if (passwordMetaData.get(Constants.RegExActiveFlag).toString().equalsIgnoreCase(Constants.no)) {
 o(String.format("Using rules to validate %s.", locale));
 int fieldValLength = val.length();
 int minNumOfChars = new Integer(passwordMetaData.get(Constants.MinNumOfChars).toString());
 int maxNumOfChars = new Integer(passwordMetaData.get(Constants.MaxNumOfChars).toString());
 int minNumOfCapLetters = new Integer(passwordMetaData.get(Constants.MinNumOfCapitalLetters).toString());
 int minNumOfSpecialChars = new Integer(passwordMetaData.get(Constants.MinNumOfSpecialChars).toString());
 int minNumOfDigits = new Integer(passwordMetaData.get(Constants.MinNumOfDigits).toString());

// minimumNumberOfCharacters
 if (minNumOfChars != 0 && fieldValLength < minNumOfChars) {
 o(String.format("Field %s, does not meet the minimum password length requirements. %n", val));
 }
 // maximumNumberOfCharacters
 else if (maxNumOfChars != 0 && fieldValLength > maxNumOfChars) {
 o(String.format("Field %s, exceeds the maximum password length allowed. %n", val));
 }
 // minimumNumberOfDigits
 else {
 // iterate over the characters and determine if it contains digits and count them
 int digitCount = 0;
 for (int i = 0; i < val.length(); i++) {
 if (((Character) val.charAt(i)).toString().matches(Constants.MinDigitsRegEx)) {
 digitCount++;
 }
 }

if (digitCount != 0 && digitCount >= minNumOfDigits) {
 o(String.format("Field %s, contains [%d] number[s]. %n", val, digitCount));
 } else {
 o(String.format("Field %s, does not contain at least one number. %n", val));
 }
 }
 // minimumNumberOfCapitalLetters
 if (minNumOfCapLetters != 0) {
 Pattern pattern = Pattern.compile(Constants.UpperCaseRegEx);
 Matcher matcher = pattern.matcher(val);
 int matchCount = 0;

while (matcher.find()) {
 matchCount += matcher.group(0).length();
 }

if (matchCount != 0 && matchCount >= minNumOfCapLetters) {
 System.out.printf("Total capital letters found was %d", matchCount);
 } else {
 o(String.format("%nField %s, does not contain at least one Capital Letter. %n", val));
 }
 }

// minimumNumberOfSpecialCharacters
 if (minNumOfSpecialChars != 0) {
 Pattern pattern = Pattern.compile(Constants.SpecialCharacters);
 Matcher matcher = pattern.matcher(val);
 int matchCount = 0;

while (matcher.find()) {
 matchCount += matcher.group(0).length();
 }

if (matchCount != 0 && (matchCount >= minNumOfCapLetters)) {
 System.out.printf("Total special characters found was %d", matchCount);
 } else {
 o(String.format("%nField %s, does not contain at least one Special Character. %n", val));
 }
 }

}
 // RegEx is active, so use it for validation
 else {
 o(String.format("Using regular expression to validate %s.", locale));
 if (!val.matches(passwordMetaData.get(Constants.RegEx).toString())) {
 o(String.format("Field %s, does not meet the minimum password requirements. %n", val));
 } else {
 o(String.format("Field %s, was validated successfully. %n", val));
 }
 }
 }

 } else {
 o(String.format("Locale: %s, is not supported for password validation.", locale));
 }
 }

@Before
 public void setUp() {
 jsonUtil = new JsonUtil();
 passwordRules = jsonUtil.loadJsonConfigurationFromFilePath(Constants.EXTERNAL_JSON_PWD_RULES);
 }

@After
 public void tearDown() {
 jsonUtil = null;
 passwordRules = null;
 }

private void o(String out) {
 System.out.printf("%s%n", out);
 }

private Map<String, Object> getPasswordMetaDataForLocale(String locale) {
 Map<String, Object> localeMap = (Map<String, Object>) passwordRules.get(locale.toUpperCase());
 Map<String, Object> fieldsMap = null;
 Map<String, Object> fieldMap = null;

if (localeMap != null) {
 fieldsMap = (Map<String, Object>) localeMap.get(Constants.FIELDS);
 } else {
 o("Locale map was null or empty...");
 return Collections.EMPTY_MAP;
 }

if (fieldsMap != null) {
 fieldMap = (Map<String, Object>) fieldsMap.get(Constants.FIELD);
 } else {
 o("Fields map was null or empty");
 return Collections.EMPTY_MAP;
 }

if (localeMap != null && fieldsMap != null && fieldMap != null) {
 if (passwordRules.containsKey(locale.toUpperCase())) {
 return fieldMap;
 }
 }

return Collections.EMPTY_MAP;
 }
}

JsonUtil.java
package processor.util;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class JsonUtil {

public Map<String, Object> loadJsonConfigurationFromFilePath(String filePath) {
 Map<String, Object> jsonMap = new HashMap<>();

try (JsonReader reader =
 new JsonReader(
 new InputStreamReader(
 new FileInputStream(new File(filePath))))) {
 reader.setLenient(true);
 Gson gson = new Gson();
 jsonMap = gson.fromJson(reader, Map.class);

} catch (IOException exception) {
 System.out.printf("IOException - %s%n", exception);
 }
 return jsonMap;
 }
}

Constants.java
package processor.util;

public class Constants {

public static final String EXTERNAL_JSON_PWD_RULES = "/metadata/PasswordRules.json";
 public static final String RegEx = "regEx";
 public static final String FieldName = "name";
 public static final String MinDigitsRegEx = "[0-9]";
 public static final String UpperCaseRegEx = "[A-Z]+";
 public static final String SpecialCharacters = "\\p{Punct}";
 public static final String FIELDS = "Fields";
 public static final String FIELD = "Field";
 public static final String RegExActiveFlag = "regExActive";
 public static final String MinNumOfChars = "minimumNumberOfCharacters";
 public static final String MaxNumOfChars = "maximumNumberOfCharacters";
 public static final String MinNumOfDigits = "minimumNumberOfDigits";
 public static final String MinNumOfCapitalLetters = "minimumNumberOfCapitalLetters";
 public static final String MinNumOfSpecialChars = "minimumNumberOfSpecialCharacters";
 public static final String no = "no";
 public static final String yes = "yes";
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s