User manager to hash with SHA-384
[controller.git] / opendaylight / usermanager / api / src / main / java / org / opendaylight / controller / usermanager / UserConfig.java
index cca194e95337e30877aaff50332e37e6df4be30f..0c14dea38a4a9ff69993d1e6a88503ceb5db0792 100644 (file)
@@ -18,35 +18,71 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
 import org.opendaylight.controller.sal.utils.HexEncode;
 import org.opendaylight.controller.sal.utils.Status;
 import org.opendaylight.controller.sal.utils.StatusCode;
-import org.opendaylight.controller.usermanager.AuthResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Configuration Java Object which represents a Local AAA user configuration
  * information for User Manager.
  */
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
 public class UserConfig implements Serializable {
     private static final long serialVersionUID = 1L;
-
-    protected String user;
-    protected List<String> roles;
-    private String password;
+    private static Logger log = LoggerFactory.getLogger(UserConfig.class);
+    private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck");
+    private static final String DIGEST_ALGORITHM = "SHA-384";
+    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 = "(?=.*[^a-zA-Z0-9])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$";
     private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)");
-    private static MessageDigest oneWayFunction = null;
+    private static MessageDigest oneWayFunction;
+
     static {
         try {
-            UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1");
+            UserConfig.oneWayFunction = MessageDigest.getInstance(DIGEST_ALGORITHM);
         } catch (NoSuchAlgorithmException e) {
-            e.printStackTrace();
+            log.error(String.format("Implementation of %s digest algorithm not found: %s", DIGEST_ALGORITHM,
+                    e.getMessage()));
         }
     }
 
+    /**
+     * User Id
+     */
+    @XmlElement
+    protected String user;
+
+    /**
+     * List of roles a user can have
+     * example
+     * System-Admin
+     * Network-Admin
+     * Network-Operator
+     */
+    @XmlElement
+    protected List<String> roles;
+
+    /**
+     * 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.
+     */
+    @XmlElement
+    private String password;
+
+
+
     public UserConfig() {
     }
 
@@ -63,16 +99,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 +174,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 +203,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 +282,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;
+    }
 }