diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java index 50f1fa00092..fc36267c0cf 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java @@ -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; + } } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java index 8c6820f8458..fb1b01e2aee 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java @@ -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(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(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(result, action); - - } else { - return new Pair(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); } + + if (!result) { + action = ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT; + } + return new Pair(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 diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java index a64899af296..95019015442 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java @@ -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(); } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java index 88f11ad12bb..76d8ce05a48 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java @@ -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 getUsers() throws NoLdapUserMatchingQueryException; List getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException; @@ -52,4 +54,6 @@ public interface LdapManager extends PluggableService { List searchUsers(String query) throws NoLdapUserMatchingQueryException; LinkDomainToLdapResponse linkDomainToLdap(Long domainId, String type, String name); + + public LdapTrustMapVO getDomainLinkedToLdap(long domainId); } \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java index d0f5d9fddd7..9d48956e592 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -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 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); + } } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java index 0a998f2655a..c4c334b5b6e 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java @@ -23,14 +23,16 @@ public class LdapUser implements Comparable { 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 { return domain; } + public boolean isDisabled() { + return disabled; + } + + @Override public int hashCode() { return getUsername().hashCode(); diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java index c1bfe742616..4e2bcf816b2 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java @@ -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 getUsers(final LdapContext context) throws NamingException, IOException; public List getUsers(final String username, final LdapContext context) throws NamingException, IOException; diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java index 11e6bcfc92a..763f1b79ef9 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java @@ -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 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 results = context.search(basedn, searchString, searchControls); + final List users = new ArrayList(); + 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 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) { diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java index c4173fe1961..7ef3799b3d8 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java @@ -23,4 +23,5 @@ import org.apache.cloudstack.ldap.LdapTrustMapVO; import com.cloud.utils.db.GenericDao; public interface LdapTrustMapDao extends GenericDao { + LdapTrustMapVO findByDomainId(long domainId); } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java index a6ce2b1053f..fb0d74d122d 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java @@ -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 implements LdapTrustMapDao { + private final SearchBuilder 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 sc = domainIdSearch.create(); + sc.setParameters("domainId", domainId); + return findOneBy(sc); } } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index 2d07e841601..a8745bb6e72 100644 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -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 actionsOnFailedAuthenticaion = new HashSet(); - 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())){