bug CS-15278: Added global setting login.attempts.allowed which defines the maximum incorrect password attempts allowed.

Also after the maximum attempts are reached the user account is disabled
This commit is contained in:
Saksham Srivastava 2012-09-12 19:16:16 +05:30 committed by Nitin Mehta
parent 41f6127fe1
commit a56631bc66
6 changed files with 78 additions and 4 deletions

View File

@ -83,6 +83,9 @@ public class UserAccountVO implements UserAccount {
@Column(name="is_registered")
boolean registered;
@Column (name="incorrect_login_attempts")
int loginAttempts;
@Column(name="account_name", table="account", insertable=false, updatable=false)
private String accountName = null;
@ -269,4 +272,12 @@ public class UserAccountVO implements UserAccount {
public void setRegistered(boolean registered) {
this.registered = registered;
}
}
public void setLoginAttempts(int loginAttempts) {
this.loginAttempts = loginAttempts;
}
public int getLoginAttempts() {
return loginAttempts;
}
}

View File

@ -229,7 +229,7 @@ public enum Config {
EnableEC2API("Advanced", ManagementServer.class, Boolean.class, "enable.ec2.api", "false", "enable EC2 API on CloudStack", null),
EnableS3API("Advanced", ManagementServer.class, Boolean.class, "enable.s3.api", "false", "enable Amazon S3 API on CloudStack", null),
RecreateSystemVmEnabled("Advanced", ManagementServer.class, Boolean.class, "recreate.systemvm.enabled", "false", "If true, will recreate system vm root disk whenever starting system vm", "true,false"),
IncorrectLoginAttemptsAllowed("Advanced", ManagementServer.class, Integer.class, "incorrect.login.attempts.allowed", "5", "Incorrect login attempts allowed before the user is disabled", null),
// Ovm
OvmPublicNetwork("Hidden", ManagementServer.class, String.class, "ovm.public.network.device", null, "Specify the public bridge on host for public network", null),
OvmPrivateNetwork("Hidden", ManagementServer.class, String.class, "ovm.private.network.device", null, "Specify the private bridge on host for private network", null),

View File

@ -274,6 +274,7 @@ public class ConfigurationManagerImpl implements ConfigurationManager, Configura
configValuesForValidation.add("storage.cleanup.interval");
configValuesForValidation.add("wait");
configValuesForValidation.add("xen.heartbeat.interval");
configValuesForValidation.add("incorrect.login.attempts.allowed");
}
@Override

View File

@ -224,6 +224,8 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AccountChecker"));
int _allowedLoginAttempts;
UserVO _systemUser;
AccountVO _systemAccount;
@Inject(adapter = SecurityChecker.class)
@ -248,6 +250,9 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
ConfigurationDao configDao = locator.getDao(ConfigurationDao.class);
Map<String, String> configs = configDao.getConfiguration(params);
String loginAttempts = configs.get(Config.IncorrectLoginAttemptsAllowed.key());
_allowedLoginAttempts = NumbersUtil.parseInt(loginAttempts, 5);
String value = configs.get(Config.AccountCleanupInterval.key());
_cleanupInterval = NumbersUtil.parseInt(value, 60 * 60 * 24); // 1 day.
@ -302,6 +307,13 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
return (accountType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN);
}
public boolean isInternalAccount(short accountType) {
if (isRootAdmin(accountType) || (accountType == Account.ACCOUNT_ID_SYSTEM)) {
return true;
}
return false;
}
@Override
public void checkAccess(Account caller, Domain domain) throws PermissionDeniedException {
for (SecurityChecker checker : _securityCheckers) {
@ -420,6 +432,25 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
}
@DB
public void updateLoginAttempts(Long id, int attempts, boolean toDisable) {
Transaction txn = Transaction.currentTxn();
txn.start();
try {
UserAccountVO user = null;
user = _userAccountDao.lockRow(id, true);
user.setLoginAttempts(attempts);
if(toDisable) {
user.setState(State.disabled.toString());
}
_userAccountDao.update(id, user);
txn.commit();
} catch (Exception e) {
s_logger.error("Failed to update login attempts for user with id " + id );
}
txn.close();
}
private boolean doSetUserStatus(long userId, State state) {
UserVO userForUpdate = _userDao.createForUpdate();
userForUpdate.setState(state);
@ -732,7 +763,7 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
if (domainId == null) {
domainId = DomainVO.ROOT_DOMAIN;
}
if (userName.isEmpty()) {
throw new InvalidParameterValueException("Username is empty");
}
@ -740,7 +771,7 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
if (firstName.isEmpty()) {
throw new InvalidParameterValueException("Firstname is empty");
}
if (lastName.isEmpty()) {
throw new InvalidParameterValueException("Lastname is empty");
}
@ -1002,6 +1033,8 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
txn.commit();
if (success) {
// whenever the user is successfully enabled, reset the login attempts to zero
updateLoginAttempts(userId, 0, false);
return _userAccountDao.findById(userId);
} else {
throw new CloudRuntimeException("Unable to enable user " + userId);
@ -1818,11 +1851,36 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag
throw new CloudAuthenticationException("User " + username + " in domain " + domainName + " is disabled/locked (or account is disabled/locked)");
// return null;
}
// Whenever the user is able to log in successfully, reset the login attempts to zero
if(!isInternalAccount(userAccount.getType()))
updateLoginAttempts(userAccount.getId(), 0, false);
return userAccount;
} else {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Unable to authenticate user with username " + username + " in domain " + domainId);
}
UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
UserAccountVO user = _userAccountDao.findById(userAccount.getId());
if (user != null) {
if ((user.getState().toString()).equals("enabled")) {
if (!isInternalAccount(user.getType())) {
//Internal accounts are not disabled
int attemptsMade = user.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 " + user.getUsername() + " has been disabled due to multiple failed login attempts." +
" Please contact admin.");
}
}
} else {
s_logger.info("User " + user.getUsername() + " is disabled/locked");
}
}
return null;
}
}

View File

@ -890,6 +890,7 @@ CREATE TABLE `cloud`.`user` (
`timezone` varchar(30) default NULL,
`registration_token` varchar(255) default NULL,
`is_registered` tinyint NOT NULL DEFAULT 0 COMMENT '1: yes, 0: no',
`incorrect_login_attempts` integer unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `i_user__removed`(`removed`),
INDEX `i_user__secret_key_removed`(`secret_key`, `removed`),

View File

@ -472,3 +472,6 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'manage
INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'site2site.vpn.customergateway.subnets.limit', '10', 'The maximum number of subnets per customer gateway');
INSERT IGNORE INTO `cloud`.`guest_os_category` VALUES ('11','None',NULL);
ALTER TABLE `cloud`.`user` ADD COLUMN `incorrect_login_attempts` integer unsigned NOT NULL DEFAULT '0';
INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'incorrect.login.attempts.allowed', '5', 'Incorrect login attempts allowed before the user is disabled');