CLOUDSTACK-8647 changed the authentication flow

added check to see if domain is linked to ldap. If yes and the user is
member of the group/OU, authenticate and import user.
This commit is contained in:
Rajani Karuturi 2015-08-10 17:31:34 +05:30 committed by Rajani Karuturi
parent e3ddde841e
commit 7109689fde
11 changed files with 203 additions and 31 deletions

View File

@ -79,4 +79,17 @@ public class ADLdapUserManagerImpl extends OpenLdapUserManagerImpl implements Ld
s_logger.debug("group search filter = " + result);
return result.toString();
}
protected boolean isUserDisabled(SearchResult result) throws NamingException {
boolean isDisabledUser = false;
String userAccountControl = LdapUtils.getAttributeValue(result.getAttributes(), _ldapConfiguration.getUserAccountControlAttribute());
if (userAccountControl != null) {
int control = Integer.valueOf(userAccountControl);
// second bit represents disabled user flag in AD
if ((control & 2) > 0) {
isDisabledUser = true;
}
}
return isDisabledUser;
}
}

View File

@ -17,6 +17,9 @@
package org.apache.cloudstack.ldap;
import com.cloud.server.auth.DefaultUserAuthenticator;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.Pair;
@ -25,6 +28,7 @@ import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.Map;
import java.util.UUID;
public class LdapAuthenticator extends DefaultUserAuthenticator {
private static final Logger s_logger = Logger.getLogger(LdapAuthenticator.class.getName());
@ -33,6 +37,8 @@ public class LdapAuthenticator extends DefaultUserAuthenticator {
private LdapManager _ldapManager;
@Inject
private UserAccountDao _userAccountDao;
@Inject
public AccountService _accountService;
public LdapAuthenticator() {
super();
@ -52,22 +58,64 @@ public class LdapAuthenticator extends DefaultUserAuthenticator {
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
}
final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
boolean result = false;
ActionOnFailedAuthentication action = null;
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;
if (_ldapManager.isLdapEnabled()) {
LdapTrustMapVO ldapTrustMapVO = _ldapManager.getDomainLinkedToLdap(domainId);
if(ldapTrustMapVO != null) {
try {
LdapUser ldapUser = _ldapManager.getUser(username, ldapTrustMapVO.getType(), ldapTrustMapVO.getName());
if(!ldapUser.isDisabled()) {
result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password);
if(result) {
final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
if (user == null) {
// import user to cloudstack
createCloudStackUserAccount(ldapUser, domainId);
}
}
} else {
//disable user in cloudstack
disableUserInCloudStack(ldapUser, domainId);
}
} catch (NoLdapUserMatchingQueryException e) {
s_logger.debug(e.getMessage());
}
} else {
//domain is not linked to ldap follow normal authentication
final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
if(user != null ) {
try {
LdapUser ldapUser = _ldapManager.getUser(username);
if(!ldapUser.isDisabled()) {
result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password);
} else {
s_logger.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap");
}
} catch (NoLdapUserMatchingQueryException e) {
s_logger.debug(e.getMessage());
}
}
}
return new Pair<Boolean, ActionOnFailedAuthentication>(result, action);
} else {
return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
}
if (!result) {
action = ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT;
}
return new Pair<Boolean, ActionOnFailedAuthentication>(result, action);
}
private void createCloudStackUserAccount(LdapUser user, long domainId) {
String username = user.getUsername();
_accountService.createUserAccount(username, "", user.getFirstname(), user.getLastname(), user.getEmail(), "GMT", username, Account.ACCOUNT_TYPE_DOMAIN_ADMIN, domainId,
username, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), User.Source.LDAP);
}
private void disableUserInCloudStack(LdapUser ldapUser, long domainId) {
final UserAccount user = _userAccountDao.getUserAccount(ldapUser.getUsername(), domainId);
_accountService.lockUser(user.getId());
}
@Override

View File

@ -108,7 +108,8 @@ public class LdapConfiguration implements Configurable{
}
public String[] getReturnAttributes() {
return new String[] {getUsernameAttribute(), getEmailAttribute(), getFirstnameAttribute(), getLastnameAttribute(), getCommonNameAttribute()};
return new String[] {getUsernameAttribute(), getEmailAttribute(), getFirstnameAttribute(), getLastnameAttribute(), getCommonNameAttribute(),
getUserAccountControlAttribute()};
}
public int getScope() {
@ -159,6 +160,10 @@ public class LdapConfiguration implements Configurable{
return "cn";
}
public String getUserAccountControlAttribute() {
return "userAccountControl";
}
public Long getReadTimeout() {
return ldapReadTimeout.value();
}

View File

@ -31,7 +31,7 @@ public interface LdapManager extends PluggableService {
LdapConfigurationResponse addConfiguration(String hostname, int port) throws InvalidParameterValueException;
boolean canAuthenticate(String username, String password);
boolean canAuthenticate(String principal, String password);
LdapConfigurationResponse createLdapConfigurationResponse(LdapConfigurationVO configuration);
@ -41,6 +41,8 @@ public interface LdapManager extends PluggableService {
LdapUser getUser(final String username) throws NoLdapUserMatchingQueryException;
LdapUser getUser(String username, String type, String name) throws NoLdapUserMatchingQueryException;
List<LdapUser> getUsers() throws NoLdapUserMatchingQueryException;
List<LdapUser> getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException;
@ -52,4 +54,6 @@ public interface LdapManager extends PluggableService {
List<LdapUser> searchUsers(String query) throws NoLdapUserMatchingQueryException;
LinkDomainToLdapResponse linkDomainToLdap(Long domainId, String type, String name);
public LdapTrustMapVO getDomainLinkedToLdap(long domainId);
}

View File

@ -105,17 +105,14 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
}
@Override
public boolean canAuthenticate(final String username, final String password) {
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
public boolean canAuthenticate(final String principal, final String password) {
try {
final LdapUser user = getUser(escapedUsername);
final String principal = user.getPrincipal();
final LdapContext context = _ldapContextFactory.createUserContext(principal, password);
closeContext(context);
return true;
} catch (NamingException | IOException | NoLdapUserMatchingQueryException e) {
s_logger.debug("Exception while doing an LDAP bind for user "+" "+username, e);
s_logger.info("Failed to authenticate user: " + username + ". incorrect password.");
} catch (NamingException | IOException e) {
s_logger.debug("Exception while doing an LDAP bind for user "+" "+principal, e);
s_logger.info("Failed to authenticate user: " + principal + ". incorrect password.");
return false;
}
}
@ -126,7 +123,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
context.close();
}
} catch (final NamingException e) {
s_logger.warn(e.getMessage(),e);
s_logger.warn(e.getMessage(), e);
}
}
@ -195,6 +192,21 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
}
}
@Override
public LdapUser getUser(final String username, final String type, final String name) throws NoLdapUserMatchingQueryException {
LdapContext context = null;
try {
context = _ldapContextFactory.createBindContext();
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider()).getUser(escapedUsername, type, name, context);
} catch (NamingException | IOException e) {
s_logger.debug("ldap Exception: ",e);
throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + "name: " + name + "of type" + type);
} finally {
closeContext(context);
}
}
@Override
public List<LdapUser> getUsers() throws NoLdapUserMatchingQueryException {
LdapContext context = null;
@ -257,4 +269,9 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
LdapTrustMapVO ldapTrustMapVO = _ldapTrustMapDao.persist(new LdapTrustMapVO(domainId, type, name));
return null;
}
@Override
public LdapTrustMapVO getDomainLinkedToLdap(long domainId){
return _ldapTrustMapDao.findByDomainId(domainId);
}
}

View File

@ -23,14 +23,16 @@ public class LdapUser implements Comparable<LdapUser> {
private final String lastname;
private final String username;
private final String domain;
private final boolean disabled;
public LdapUser(final String username, final String email, final String firstname, final String lastname, final String principal, String domain) {
public LdapUser(final String username, final String email, final String firstname, final String lastname, final String principal, String domain, boolean disabled) {
this.username = username;
this.email = email;
this.firstname = firstname;
this.lastname = lastname;
this.principal = principal;
this.domain = domain;
this.disabled = disabled;
}
@Override
@ -74,6 +76,11 @@ public class LdapUser implements Comparable<LdapUser> {
return domain;
}
public boolean isDisabled() {
return disabled;
}
@Override
public int hashCode() {
return getUsername().hashCode();

View File

@ -32,6 +32,8 @@ public interface LdapUserManager {
public LdapUser getUser(final String username, final LdapContext context) throws NamingException, IOException;
public LdapUser getUser(final String username, final String type, final String name, final LdapContext context) throws NamingException, IOException;
public List<LdapUser> getUsers(final LdapContext context) throws NamingException, IOException;
public List<LdapUser> getUsers(final String username, final LdapContext context) throws NamingException, IOException;

View File

@ -63,7 +63,9 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
domain = domain.replace("," + _ldapConfiguration.getBaseDn(), "");
domain = domain.replace("ou=", "");
return new LdapUser(username, email, firstname, lastname, principal, domain);
boolean disabled = isUserDisabled(result);
return new LdapUser(username, email, firstname, lastname, principal, domain, disabled);
}
private String generateSearchFilter(final String username) {
@ -128,6 +130,43 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
}
}
@Override
public LdapUser getUser(final String username, final String type, final String name, final LdapContext context) throws NamingException, IOException {
String basedn;
if("OU".equals(type)) {
basedn = name;
} else {
basedn = _ldapConfiguration.getBaseDn();
}
final StringBuilder userObjectFilter = new StringBuilder();
userObjectFilter.append("(objectClass=");
userObjectFilter.append(_ldapConfiguration.getUserObject());
userObjectFilter.append(")");
final StringBuilder usernameFilter = new StringBuilder();
usernameFilter.append("(");
usernameFilter.append(_ldapConfiguration.getUsernameAttribute());
usernameFilter.append("=");
usernameFilter.append((username == null ? "*" : username));
usernameFilter.append(")");
final StringBuilder memberOfFilter = new StringBuilder();
if ("GROUP".equals(type)) {
memberOfFilter.append("(memberof=");
memberOfFilter.append(name);
memberOfFilter.append(")");
}
final StringBuilder searchQuery = new StringBuilder();
searchQuery.append("(&");
searchQuery.append(userObjectFilter);
searchQuery.append(usernameFilter);
searchQuery.append(memberOfFilter);
searchQuery.append(")");
return searchUser(basedn, searchQuery.toString(), context);
}
@Override
public List<LdapUser> getUsers(final LdapContext context) throws NamingException, IOException {
return getUsers(null, context);
@ -191,6 +230,30 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
return searchUsers(null, context);
}
protected boolean isUserDisabled(SearchResult result) throws NamingException {
return false;
}
public LdapUser searchUser(final String basedn, final String searchString, final LdapContext context) throws NamingException, IOException {
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(_ldapConfiguration.getScope());
searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes());
NamingEnumeration<SearchResult> results = context.search(basedn, searchString, searchControls);
final List<LdapUser> users = new ArrayList<LdapUser>();
while (results.hasMoreElements()) {
final SearchResult result = results.nextElement();
users.add(createUser(result));
}
if (users.size() == 1) {
return users.get(0);
} else {
throw new NamingException("No user found for basedn " + basedn + " and searchString " + searchString);
}
}
@Override
public List<LdapUser> searchUsers(final String username, final LdapContext context) throws NamingException, IOException {
@ -212,7 +275,9 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
results = context.search(basedn, generateSearchFilter(username), searchControls);
while (results.hasMoreElements()) {
final SearchResult result = results.nextElement();
users.add(createUser(result));
if (!isUserDisabled(result)) {
users.add(createUser(result));
}
}
Control[] contextControls = context.getResponseControls();
if (contextControls != null) {

View File

@ -23,4 +23,5 @@ import org.apache.cloudstack.ldap.LdapTrustMapVO;
import com.cloud.utils.db.GenericDao;
public interface LdapTrustMapDao extends GenericDao<LdapTrustMapVO, Long> {
LdapTrustMapVO findByDomainId(long domainId);
}

View File

@ -20,6 +20,8 @@ package org.apache.cloudstack.ldap.dao;
import javax.ejb.Local;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.ldap.LdapTrustMapVO;
import org.springframework.stereotype.Component;
@ -28,7 +30,19 @@ import com.cloud.utils.db.GenericDaoBase;
@Component
@Local(value = {LdapTrustMapDao.class})
public class LdapTrustMapDaoImpl extends GenericDaoBase<LdapTrustMapVO, Long> implements LdapTrustMapDao {
private final SearchBuilder<LdapTrustMapVO> domainIdSearch;
public LdapTrustMapDaoImpl() {
super();
domainIdSearch = createSearchBuilder();
domainIdSearch.and("domainId", domainIdSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
domainIdSearch.done();
}
@Override
public LdapTrustMapVO findByDomainId(long domainId) {
final SearchCriteria<LdapTrustMapVO> sc = domainIdSearch.create();
sc.setParameters("domainId", domainId);
return findOneBy(sc);
}
}

View File

@ -2145,14 +2145,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
s_logger.debug("Attempting to log in user: " + username + " in domain " + domainId);
}
UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
if (userAccount == null) {
s_logger.warn("Unable to find an user with username " + username + " in domain " + domainId);
return null;
}
boolean authenticated = false;
HashSet<ActionOnFailedAuthentication> actionsOnFailedAuthenticaion = new HashSet<ActionOnFailedAuthentication>();
User.Source userSource = userAccount.getSource();
User.Source userSource = userAccount != null ? userAccount.getSource(): User.Source.UNKNOWN;
for (UserAuthenticator authenticator : _userAuthenticators) {
if(userSource != User.Source.UNKNOWN) {
if(!authenticator.getName().equalsIgnoreCase(userSource.name())){