Add strong password check for users 41/1241/2
authorAlessandro Boch <aboch@cisco.com>
Wed, 18 Sep 2013 07:13:49 +0000 (00:13 -0700)
committerAlessandro Boch <aboch@cisco.com>
Wed, 18 Sep 2013 07:53:28 +0000 (00:53 -0700)
- User password must be at least 8 characters long, contain upper and lower
  case letters, at least one number and one non-alphanumeric character. Max lenght 256.
- Disabled by default, can be enabled through config.ini

Change-Id: I4696801dc60379292a3d6a687795f33971bd85c8
Signed-off-by: Alessandro Boch <aboch@cisco.com>
opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini
opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java
opendaylight/usermanager/api/src/test/java/org/opendaylight/controller/usermanager/AuthorizationUserConfigTest.java
opendaylight/usermanager/implementation/src/main/java/org/opendaylight/controller/usermanager/internal/Activator.java
opendaylight/usermanager/implementation/src/main/java/org/opendaylight/controller/usermanager/internal/UserManager.java

index 970929f..12f18ff 100644 (file)
@@ -94,3 +94,6 @@ controllerKeyStore=
 controllerKeyStorePassword=
 controllerTrustStore=
 controllerTrustStorePassword=
+
+# User Manager configurations
+enableStrongPasswordCheck = false
index cca194e..07c814a 100644 (file)
@@ -34,9 +34,11 @@ public class UserConfig implements Serializable {
     protected String user;
     protected List<String> roles;
     private String password;
+
+    private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck");
+    private static final String BAD_PASSWORD = "Bad Password";
     private static final int USERNAME_MAXLENGTH = 32;
-    private static final int PASSWORD_MINLENGTH = 5;
-    private static final int PASSWORD_MAXLENGTH = 256;
+    protected static final String PASSWORD_REGEX = "(?=.*[^\\w])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$";
     private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)");
     private static MessageDigest oneWayFunction = null;
     static {
@@ -63,16 +65,12 @@ public class UserConfig implements Serializable {
     public UserConfig(String user, String password, List<String> roles) {
         this.user = user;
 
-        this.password = password;
-        if (this.validatePassword().isSuccess()) {
-            /*
-             * Only if the password is a valid one, hash it. So in case it is not
-             * valid, when UserConfig.validate() is called, the proper
-             * validation error will be returned to the caller. If we hashed a
-             * priori instead, the mis-configuration would be masked
-             */
-            this.password = hash(this.password);
-        }
+        /*
+         * Password validation to be done on clear text password. If fails, mark
+         * the password with a well known label, so that object validation can
+         * report the proper error. Only if password is a valid one, hash it.
+         */
+        this.password = (validatePassword(password).isSuccess()) ? hash(password) : BAD_PASSWORD;
 
         this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
     }
@@ -142,12 +140,15 @@ public class UserConfig implements Serializable {
     }
 
     public Status validate() {
-        Status validCheck = validateRoles();
+        Status validCheck = validateUsername();
         if (validCheck.isSuccess()) {
-            validCheck = validateUsername();
+            validCheck = (!password.equals(BAD_PASSWORD)) ? new Status(StatusCode.SUCCESS) : new Status(
+                    StatusCode.BADREQUEST,
+                    "Password should be 8 to 256 characters long, contain both upper and lower case letters, "
+                            + "at least one number and at least one non alphanumeric character");
         }
         if (validCheck.isSuccess()) {
-            validCheck = validatePassword();
+            validCheck = validateRoles();
         }
         return validCheck;
     }
@@ -168,15 +169,15 @@ public class UserConfig implements Serializable {
         return new Status(StatusCode.SUCCESS);
     }
 
-    private Status validatePassword() {
+    private Status validatePassword(String password) {
         if (password == null || password.isEmpty()) {
             return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
         }
 
-        if (password.length() < UserConfig.PASSWORD_MINLENGTH
-                || password.length() > UserConfig.PASSWORD_MAXLENGTH) {
-            return new Status(StatusCode.BADREQUEST,
-                    "Password should have 5-256 characters");
+        if (strongPasswordCheck && !password.matches(UserConfig.PASSWORD_REGEX)) {
+            return new Status(StatusCode.BADREQUEST, "Password should be 8 to 256 characters long, "
+                    + "contain both upper and lower case letters, at least one number "
+                    + "and at least one non alphanumeric character");
         }
         return new Status(StatusCode.SUCCESS);
     }
@@ -247,4 +248,25 @@ public class UserConfig implements Serializable {
         UserConfig.oneWayFunction.reset();
         return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));
     }
+
+    /**
+     * Returns UserConfig instance populated with the passed parameters. It does
+     * not run any checks on the passed parameters.
+     *
+     * @param userName
+     *            the user name
+     * @param password
+     *            the plain text password
+     * @param roles
+     *            the list of roles
+     * @return the UserConfig object populated with the passed parameters. No
+     *         validity check is run on the input parameters.
+     */
+    public static UserConfig getUncheckedUserConfig(String userName, String password, List<String> roles) {
+        UserConfig config = new UserConfig();
+        config.user = userName;
+        config.password = hash(password);
+        config.roles = roles;
+        return config;
+    }
 }
index 4c2a19e..8c029a7 100644 (file)
@@ -117,4 +117,41 @@ public class AuthorizationUserConfigTest {
         UserConfig userConfig2 = new UserConfig("uname", "ciscocisco", roles);
         assertEquals(userConfig, userConfig2);
     }
+
+    @Test
+    public void userConfigPasswordTest() {
+
+        String regex = UserConfig.PASSWORD_REGEX;
+        String password = null;
+
+        // Good password
+        password = "aBc@eF#h9";
+        assertTrue(password.matches(regex));
+        password = "^aBc@eF#h9$88ad*o&";
+        assertTrue(password.matches(regex));
+        password = "_^aBc@\":eF#h;9$\\8|8ad*o&-(){}/,.><?+-";
+        assertTrue(password.matches(regex));
+        password = "culonE1)";
+        assertTrue(password.matches(regex));
+
+        // Too short
+        password = "aB3@eF#";
+        assertFalse(password.matches(regex));
+
+        // No number
+        password = "#BeCCC#CeDfDf";
+        assertFalse(password.matches(regex));
+
+        // No lower case
+        password = "AB8C#CC@C4";
+        assertFalse(password.matches(regex));
+
+        // No upper case
+        password = "ab8defg9!";
+        assertFalse(password.matches(regex));
+
+        // No special characters
+        password = "aBc4ef7H8";
+        assertFalse(password.matches(regex));
+    }
 }
index e53e962..8d9e347 100644 (file)
@@ -204,12 +204,17 @@ public class UserManager implements IUserManager, IObjectReader,
     }
 
     private void checkDefaultNetworkAdmin() {
-        // If startup config is not there, it's old or it was deleted,
-        // need to add Default Network Admin User
+        /*
+         * If startup config is not there, it's old or it was deleted or if a
+         * password recovery was run, need to add Default Network Admin User
+         */
         if (!localUserConfigList.containsKey(DEFAULT_ADMIN)) {
             List<String> roles = new ArrayList<String>(1);
             roles.add(DEFAULT_ADMIN_ROLE);
-            localUserConfigList.put(DEFAULT_ADMIN, new UserConfig(DEFAULT_ADMIN, DEFAULT_ADMIN_PASSWORD, roles));
+            // Need to skip the strong password check for the default admin
+            UserConfig defaultAdmin = UserConfig.getUncheckedUserConfig(UserManager.DEFAULT_ADMIN,
+                    UserManager.DEFAULT_ADMIN_PASSWORD, roles);
+            localUserConfigList.put(UserManager.DEFAULT_ADMIN, defaultAdmin);
         }
     }