mirror of https://github.com/apache/cloudstack.git
Changed "authenticate" method to return both - result of authentication, and action to perform when authentication failed - to the accountManagerImpl. Only if authenicators request INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT, the incorrect_login_attempts parameter will be increased
Signed-off-by: Alena Prokharchyk <alena.prokharchyk@citrix.com> Conflicts: plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java plugins/user-authenticators/md5/src/com/cloud/server/auth/MD5UserAuthenticator.java plugins/user-authenticators/plain-text/src/com/cloud/server/auth/PlainTextUserAuthenticator.java plugins/user-authenticators/sha256salted/src/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java server/src/com/cloud/server/auth/UserAuthenticator.java server/src/com/cloud/user/AccountManagerImpl.java
This commit is contained in:
parent
af5f3d5676
commit
7884bb8aaf
|
|
@ -25,6 +25,7 @@ import org.apache.log4j.Logger;
|
|||
import com.cloud.server.auth.DefaultUserAuthenticator;
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
public class LdapAuthenticator extends DefaultUserAuthenticator {
|
||||
private static final Logger s_logger = Logger
|
||||
|
|
@ -46,23 +47,27 @@ public class LdapAuthenticator extends DefaultUserAuthenticator {
|
|||
_userAccountDao = userAccountDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(final String username, final String password,
|
||||
final Long domainId, final Map<String, Object[]> requestParameters) {
|
||||
@Override
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(final String username, final String password, final Long domainId, final Map<String, Object[]> requestParameters) {
|
||||
|
||||
final UserAccount user = _userAccountDao.getUserAccount(username,
|
||||
domainId);
|
||||
|
||||
if (user == null) {
|
||||
s_logger.debug("Unable to find user with " + username
|
||||
+ " in domain " + domainId);
|
||||
return false;
|
||||
} else if (_ldapManager.isLdapEnabled()) {
|
||||
return _ldapManager.canAuthenticate(username, password);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (user == null) {
|
||||
s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
} else if (_ldapManager.isLdapEnabled()) {
|
||||
boolean result = _ldapManager.canAuthenticate(username, password);
|
||||
ActionOnFailedAuthentication action = null;
|
||||
if (result == false) {
|
||||
action = ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT;
|
||||
}
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(result, action);
|
||||
|
||||
} else {
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(final String password) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.apache.log4j.Logger;
|
|||
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
/**
|
||||
|
|
@ -34,47 +35,48 @@ import com.cloud.utils.exception.CloudRuntimeException;
|
|||
* comparing it against the local database.
|
||||
*
|
||||
*/
|
||||
@Local(value={UserAuthenticator.class})
|
||||
@Local(value = {UserAuthenticator.class})
|
||||
public class MD5UserAuthenticator extends DefaultUserAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(MD5UserAuthenticator.class);
|
||||
|
||||
@Inject private UserAccountDao _userAccountDao;
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters ) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
public static final Logger s_logger = Logger.getLogger(MD5UserAuthenticator.class);
|
||||
|
||||
@Inject
|
||||
private UserAccountDao _userAccountDao;
|
||||
|
||||
@Override
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Retrieving user: " + username);
|
||||
}
|
||||
UserAccount user = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (user == null) {
|
||||
s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
|
||||
return false;
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
}
|
||||
|
||||
|
||||
if (!user.getPassword().equals(encode(password))) {
|
||||
s_logger.debug("Password does not match");
|
||||
return false;
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
|
||||
}
|
||||
|
||||
public String encode(String password) {
|
||||
MessageDigest md5 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CloudRuntimeException("Unable to hash password", e);
|
||||
}
|
||||
public String encode(String password) {
|
||||
MessageDigest md5 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CloudRuntimeException("Unable to hash password", e);
|
||||
}
|
||||
|
||||
md5.reset();
|
||||
BigInteger pwInt = new BigInteger(1, md5.digest(password.getBytes()));
|
||||
String pwStr = pwInt.toString(16);
|
||||
int padding = 32 - pwStr.length();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < padding; i++) {
|
||||
sb.append('0'); // make sure the MD5 password is 32 digits long
|
||||
}
|
||||
sb.append(pwStr);
|
||||
return sb.toString();
|
||||
}
|
||||
md5.reset();
|
||||
BigInteger pwInt = new BigInteger(1, md5.digest(password.getBytes()));
|
||||
String pwStr = pwInt.toString(16);
|
||||
int padding = 32 - pwStr.length();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < padding; i++) {
|
||||
sb.append('0'); // make sure the MD5 password is 32 digits long
|
||||
}
|
||||
sb.append(pwStr);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,36 +24,38 @@ import org.apache.log4j.Logger;
|
|||
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
|
||||
@Local(value={UserAuthenticator.class})
|
||||
@Local(value = {UserAuthenticator.class})
|
||||
public class PlainTextUserAuthenticator extends DefaultUserAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(PlainTextUserAuthenticator.class);
|
||||
|
||||
@Inject private UserAccountDao _userAccountDao;
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters ) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
public static final Logger s_logger = Logger.getLogger(PlainTextUserAuthenticator.class);
|
||||
|
||||
@Inject
|
||||
private UserAccountDao _userAccountDao;
|
||||
|
||||
@Override
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Retrieving user: " + username);
|
||||
}
|
||||
|
||||
UserAccount user = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (user == null) {
|
||||
s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
|
||||
return false;
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
}
|
||||
|
||||
|
||||
if (!user.getPassword().equals(password)) {
|
||||
s_logger.debug("Password does not match");
|
||||
return false;
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String password) {
|
||||
// Plaintext so no encoding at all
|
||||
return password;
|
||||
}
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String password) {
|
||||
// Plaintext so no encoding at all
|
||||
return password;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,10 @@ import org.bouncycastle.util.encoders.Base64;
|
|||
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
@Local(value={UserAuthenticator.class})
|
||||
@Local(value = {UserAuthenticator.class})
|
||||
public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(SHA256SaltedUserAuthenticator.class);
|
||||
private static final String s_defaultPassword = "000000000000000000000000000=";
|
||||
|
|
@ -45,8 +46,7 @@ public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
|||
* @see com.cloud.server.auth.UserAuthenticator#authenticate(java.lang.String, java.lang.String, java.lang.Long, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public boolean authenticate(String username, String password,
|
||||
Long domainId, Map<String, Object[]> requestParameters) {
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Retrieving user: " + username);
|
||||
}
|
||||
|
|
@ -72,7 +72,12 @@ public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
|||
try {
|
||||
String hashedPassword = encode(password, salt);
|
||||
/* constantTimeEquals comes first in boolean since we need to thwart timing attacks */
|
||||
return constantTimeEquals(realPassword, hashedPassword) && realUser;
|
||||
boolean result = constantTimeEquals(realPassword, hashedPassword) && realUser;
|
||||
ActionOnFailedAuthentication action = null;
|
||||
if (!result && realUser) {
|
||||
action = ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT;
|
||||
}
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(result, action);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CloudRuntimeException("Unable to hash password", e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
|
|
|||
|
|
@ -18,26 +18,29 @@ package com.cloud.server.auth;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.component.Adapter;
|
||||
|
||||
/**
|
||||
* which UserAuthenticator to user in components.xml.
|
||||
*
|
||||
*/
|
||||
public interface UserAuthenticator extends Adapter {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @param domainId
|
||||
* @return true if the user has been successfully authenticated, false otherwise
|
||||
*/
|
||||
public boolean authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters);
|
||||
|
||||
/**
|
||||
* @param password
|
||||
* @return the encoded password
|
||||
*/
|
||||
public String encode(String password);
|
||||
public enum ActionOnFailedAuthentication {
|
||||
INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @param domainId
|
||||
* @return the pair of 2 booleans - first identifies the success of authenciation, the second - whether to increase incorrect login attempts count in case of failed authentication
|
||||
*/
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters);
|
||||
|
||||
/**
|
||||
* @param password
|
||||
* @return the encoded password
|
||||
*/
|
||||
public String encode(String password);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -110,6 +111,7 @@ import com.cloud.projects.ProjectVO;
|
|||
import com.cloud.projects.dao.ProjectAccountDao;
|
||||
import com.cloud.projects.dao.ProjectDao;
|
||||
import com.cloud.server.auth.UserAuthenticator;
|
||||
import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication;
|
||||
import com.cloud.storage.VMTemplateVO;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeApiService;
|
||||
|
|
@ -1960,13 +1962,19 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||
}
|
||||
|
||||
boolean authenticated = false;
|
||||
HashSet<ActionOnFailedAuthentication> actionsOnFailedAuthenticaion = new HashSet<ActionOnFailedAuthentication>();
|
||||
for (UserAuthenticator authenticator : _userAuthenticators) {
|
||||
if (authenticator.authenticate(username, password, domainId, requestParameters)) {
|
||||
Pair<Boolean, ActionOnFailedAuthentication> result = authenticator.authenticate(username, password, domainId, requestParameters);
|
||||
if (result.first()) {
|
||||
authenticated = true;
|
||||
break;
|
||||
} else if (result.second() != null) {
|
||||
actionsOnFailedAuthenticaion.add(result.second());
|
||||
}
|
||||
}
|
||||
|
||||
boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion.contains(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
|
||||
if (authenticated) {
|
||||
UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (userAccount == null) {
|
||||
|
|
@ -2003,13 +2011,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||
if (!isInternalAccount(userAccount.getType())) {
|
||||
// Internal accounts are not disabled
|
||||
int attemptsMade = userAccount.getLoginAttempts() + 1;
|
||||
if (attemptsMade < _allowedLoginAttempts) {
|
||||
updateLoginAttempts(userAccount.getId(), attemptsMade, false);
|
||||
s_logger.warn("Login attempt failed. You have " + (_allowedLoginAttempts - attemptsMade) + " attempt(s) remaining");
|
||||
} else {
|
||||
updateLoginAttempts(userAccount.getId(), _allowedLoginAttempts, true);
|
||||
s_logger.warn("User " + userAccount.getUsername() + " has been disabled due to multiple failed login attempts." +
|
||||
" Please contact admin.");
|
||||
if (updateIncorrectLoginCount) {
|
||||
if (attemptsMade < _allowedLoginAttempts) {
|
||||
updateLoginAttempts(userAccount.getId(), attemptsMade, false);
|
||||
s_logger.warn("Login attempt failed. You have " + (_allowedLoginAttempts - attemptsMade) + " attempt(s) remaining");
|
||||
} else {
|
||||
updateLoginAttempts(userAccount.getId(), _allowedLoginAttempts, true);
|
||||
s_logger.warn("User " + userAccount.getUsername() + " has been disabled due to multiple failed login attempts." + " Please contact admin.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue