I recently wrote a user self-registration solution for SharePoint 2010. As part of this solution, I needed to validate the requested password to ensure that it met the requirements of the authentication provider (in this case, Active Directory).
The code, while hardly rocket science, is something I do not want to figure out again. So I thought I would post it here for my own benefit. If anyone else finds it useful, that’s cool, too!
I wanted to validate that the password met various requirements from the AD policy:
- 1. Length
- 2. Complexity (contains a combination of lower case, upper case, digits, and special characters.
- 3. Content (does not contain all or part of user name)
I did not need to check against old-passwords to prevent repetition, since this solution is creating new users.
Checking for length is blatantly obvious, so I won’t bother showing that.
For complexity, the default AD policy is as follows:
“Passwords must contain characters from three of the following four categories:
- English uppercase characters (A through Z).
- English lowercase characters (a through z).
- Base 10 digits (0 through 9).
- Non-alphabetic characters (for example, !, $, #, %).”
This is pretty simple to do. In fact there are several ways to do it, depending on whether you want to use regular expressions, built-in methods of the string class, etc.
- For digits and special symbols, I simply created character arrays for those two groups, and used the string class’ IndexOfAny method.
- I could have done the upper and lower case the same way, but I decided to take a different approach (just for variety). For a string S, if S==S.ToLowerCase(), then S contains no uppercase letters. Similarly for uppercase.
- Having determined the presence of the 4 classes of characters, I can then simply add up the number of character classes found.
So, code I have used for these conditions is:
The next interesting part was the content check. To check that no more than 2 consecutive letters of the user name or full name are used in the password, I iterate over the username, taking three-character substrings, and checking to see if the password contains them. This is then repeated for the full name. If any of the substrings is found, then the password fails:
1: protected bool PasswordComplexityValidation(string UserName, string FullName, string Password)
2: {
3: char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
4: char[] symbols = { '~', '!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '+', '=', '`', '|', '\\', '(', ')', '{', '}', '[', ']', ':', ';', '"', '<', '>', ',', '.', '?', '/' };
5:
6: bool blnHasDigits = (Password.IndexOfAny(digits) != -1);
7: bool blnHasSymbols = (Password.IndexOfAny(symbols) != -1);
8: bool blnHasUpperCase = !(Password.Equals(Password.ToLower()));
9: bool blnHasLowerCase = !(Password.Equals(Password.ToUpper()));
10:
11: int conditionsMet = 0;
12:
13: if (blnHasDigits)
14: conditionsMet++;
15:
16: if (blnHasSymbols)
17: conditionsMet++;
18:
19: if (blnHasUpperCase)
20: conditionsMet++;
21:
22: if (blnHasLowerCase)
23: conditionsMet++;
24:
25: return (conditionsMet > 2);
26: }
That is about it. I make no representations that this is the best way of validating passwords, or even the best. This is just what I thought up over the weekend!
1: protected bool PasswordContentValidation(string UserName, string FullName, string Password)
2: {
3: // Check that password does not contain a large part of the UserName or Full Name
4:
5: bool blnIsValid = true;
6:
7: // Check if password contains a 3 character substring from the username
8: for (int i = 0; i < (UserName.Length - 3); i++)
9: {
10: string substring = UserName.Substring(i, 3);
11: if (Password.Contains(substring))
12: {
13: blnIsValid = false;
14: break;
15: }
16: }
17:
18: // Do the same for 3-character substrings from the Full Name, but only if the password is not already invalid
19: if (blnIsValid)
20: {
21: for (int i = 0; i < (FullName.Length - 3); i++)
22: {
23: string substring = FullName.Substring(i, 3);
24: if (Password.Contains(substring))
25: {
26: blnIsValid = false;
27: break;
28: }
29: }
30: }
31: return blnIsValid;
32: }