diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java index f872247f07e..063db0e5ae1 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.ldap.LdapManager; import org.apache.cloudstack.ldap.LdapUser; import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.util.encoders.Base64; @@ -55,6 +56,15 @@ public class LdapImportUsersCmd extends BaseListCmd { @Parameter(name = ApiConstants.ACCOUNT_DETAILS, type = CommandType.MAP, description = "details for account used to store specific parameters") private Map details; + @Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, + description = "Specifies the domain to which the ldap users are to be imported. If no domain is specified, a domain will created using group parameter. If the " + + "group is also not specified, a domain name based on the OU information will be created. If no OU hierarchy exists, will be defaulted to ROOT domain") + private String domainName; + + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, + description = "Specifies the group name from which the ldap users are to be imported. If no group is specified, all the users will be imported.") + private String groupName; + @Inject private LdapManager _ldapManager; @@ -75,14 +85,15 @@ public class LdapImportUsersCmd extends BaseListCmd { List ldapResponses = null; final ListResponse response = new ListResponse(); try { - final List users = _ldapManager.getUsers(); + List users; + if(StringUtils.isNotBlank(groupName)) { + users = _ldapManager.getUsersInGroup(groupName); + } else { + users = _ldapManager.getUsers(); + } for (LdapUser user : users) { - Domain domain = _domainService.getDomainByName(user.getDomain(), Domain.ROOT_DOMAIN); - - if (domain == null) { - domain = _domainService.createDomain(user.getDomain(), Domain.ROOT_DOMAIN, user.getDomain(), UUID.randomUUID().toString()); - } - _accountService.createUserAccount(user.getUsername(), generatePassword(), user.getFirstname(), user.getLastname(), user.getEmail(), timezone, user.getUsername(), + Domain domain = getDomain(user); + _accountService.createUserAccount(user.getUsername(), generatePassword(), user.getFirstname(), user.getLastname(), user.getEmail(), timezone, user.getUsername(), accountType, domain.getId(), domain.getNetworkDomain(), details, UUID.randomUUID().toString(), UUID.randomUUID().toString()); } ldapResponses = createLdapUserResponse(users); @@ -95,6 +106,33 @@ public class LdapImportUsersCmd extends BaseListCmd { } } + private Domain getDomain(LdapUser user) { + String csDomainName = null; + if (StringUtils.isNotBlank(domainName)) { + csDomainName = domainName; + } else { + if (StringUtils.isNotBlank(groupName)) { + csDomainName = groupName; + } else if (StringUtils.isNotBlank(user.getDomain())) { + csDomainName = user.getDomain(); + } + //removing all the special characters and trimming it length 190 to make the domain valid. + csDomainName = StringUtils.substring(csDomainName.replaceAll("\\W",""),0,190); + } + Domain domain; + if (StringUtils.isNotBlank(csDomainName)) { + domain = _domainService.getDomainByName(csDomainName, Domain.ROOT_DOMAIN); + + if (domain == null) { + domain = _domainService.createDomain(csDomainName, Domain.ROOT_DOMAIN, csDomainName, UUID.randomUUID().toString()); + } + } else { + domain = _domainService.getDomain(Domain.ROOT_DOMAIN); + } + + return domain; + } + private List createLdapUserResponse(List users) { final List ldapResponses = new ArrayList(); for (final LdapUser user : users) { 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 a08dccbd412..7db55f74a08 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 @@ -143,6 +143,16 @@ public class LdapConfiguration { return userObject == null ? "inetOrgPerson" : userObject; } + public String getGroupObject() { + final String groupObject = _configDao.getValue("ldap.group.object"); + return groupObject == null ? "groupOfUniqueNames" : groupObject; + } + + public String getGroupUniqueMemeberAttribute() { + final String uniqueMemberAttribute = _configDao.getValue("ldap.group.user.uniquemember"); + return uniqueMemberAttribute == null ? "uniquemember" : uniqueMemberAttribute; + } + public String getCommonNameAttribute() { return "cn"; } 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 683822d31f4..2c99d080b9a 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 @@ -47,6 +47,8 @@ public interface LdapManager extends PluggableService { List getUsers() throws NoLdapUserMatchingQueryException; + List getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException; + boolean isLdapEnabled(); Pair, Integer> listConfigurations( 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 90a79b3f96a..891d62538ab 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 @@ -194,7 +194,20 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { } } - @Override + @Override + public List getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException { + DirContext context = null; + try { + context = _ldapContextFactory.createBindContext(); + return _ldapUserManager.getUsersInGroup(groupName, context); + } catch (final NamingException e) { + throw new NoLdapUserMatchingQueryException("groupName=" + groupName); + } finally { + closeContext(context); + } + } + + @Override public boolean isLdapEnabled() { return listConfigurations(new LdapListConfigurationCmd(this)).second() > 0; } 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 47697c9127e..59a41dee75f 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 @@ -23,10 +23,7 @@ import java.util.List; import javax.inject.Inject; import javax.naming.NamingEnumeration; import javax.naming.NamingException; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; +import javax.naming.directory.*; public class LdapUserManager { @@ -86,6 +83,28 @@ public class LdapUserManager { return result.toString(); } + private String generateGroupSearchFilter(final String groupName) { + final StringBuilder groupObjectFilter = new StringBuilder(); + groupObjectFilter.append("(objectClass="); + groupObjectFilter.append(_ldapConfiguration.getGroupObject()); + groupObjectFilter.append(")"); + + final StringBuilder groupNameFilter = new StringBuilder(); + groupNameFilter.append("("); + groupNameFilter.append(_ldapConfiguration.getCommonNameAttribute()); + groupNameFilter.append("="); + groupNameFilter.append((groupName == null ? "*" : groupName)); + groupNameFilter.append(")"); + + final StringBuilder result = new StringBuilder(); + result.append("(&"); + result.append(groupObjectFilter); + result.append(groupNameFilter); + result.append(")"); + + return result.toString(); + } + public LdapUser getUser(final String username, final DirContext context) throws NamingException { final NamingEnumeration result = searchUsers(username, context); if (result.hasMoreElements()) { @@ -114,6 +133,44 @@ public class LdapUserManager { return users; } + public List getUsersInGroup(String groupName, DirContext context) throws NamingException { + String attributeName = _ldapConfiguration.getGroupUniqueMemeberAttribute(); + final SearchControls controls = new SearchControls(); + controls.setSearchScope(_ldapConfiguration.getScope()); + controls.setReturningAttributes(new String[]{attributeName}); + + NamingEnumeration result = context.search(_ldapConfiguration.getBaseDn(), generateGroupSearchFilter(groupName), controls); + + final List users = new ArrayList(); + //Expecting only one result which has all the users + if (result.hasMoreElements()) { + Attribute attribute = result.nextElement().getAttributes().get(attributeName); + NamingEnumeration values = attribute.getAll(); + + while (values.hasMoreElements()) { + String userdn = String.valueOf(values.nextElement()); + users.add(getUserForDn(userdn,context)); + } + } + + Collections.sort(users); + + return users; + } + + private LdapUser getUserForDn(String userdn, DirContext context) throws NamingException { + final SearchControls controls = new SearchControls(); + controls.setSearchScope(_ldapConfiguration.getScope()); + controls.setReturningAttributes(_ldapConfiguration.getReturnAttributes()); + + NamingEnumeration result = context.search(userdn, "(objectClass="+_ldapConfiguration.getUserObject()+")", controls); + if (result.hasMoreElements()) { + return createUser(result.nextElement()); + } else { + throw new NamingException("No user found for dn " + userdn); + } + } + public NamingEnumeration searchUsers(final DirContext context) throws NamingException { return searchUsers(null, context); } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy index 66b4673b258..1017a0fa0f9 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy @@ -220,4 +220,37 @@ class LdapConfigurationSpec extends spock.lang.Specification { then: "The response should be true" result == true } + + def "Test getgroupobject"() { + given: "We have configdao for ldap group object" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.group.object") >> groupObject + + def ldapManger = Mock(LdapManager) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManger) + def expectedResult = groupObject == null ? "groupOfUniqueNames" : groupObject + + def result = ldapConfiguration.getGroupObject() + expect: + result == expectedResult + where: + groupObject << [null, "", "groupOfUniqueNames"] + } + + def "Test getGroupUniqueMemeberAttribute"() { + given: "We have configdao for ldap group object" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.group.user.uniquemember") >> groupObject + + def ldapManger = Mock(LdapManager) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManger) + def expectedResult = groupObject == null ? "uniquemember" : groupObject + + def result = ldapConfiguration.getGroupUniqueMemeberAttribute() + expect: + result == expectedResult + where: + groupObject << [null, "", "uniquemember"] + } + } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy index cc849defef5..b316a80a824 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy @@ -52,7 +52,7 @@ class LdapCreateAccountCmdSpec extends spock.lang.Specification { def "Test failed creation due to a null response from cloudstack account creater"() { given: "We have an LdapManager, AccountService and LdapCreateAccountCmd" LdapManager ldapManager = Mock(LdapManager) - ldapManager.getUser(_) >> new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + ldapManager.getUser(_) >> new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") AccountService accountService = Mock(AccountService) def ldapCreateAccountCmd = Spy(LdapCreateAccountCmd, constructorArgs: [ldapManager, accountService]) ldapCreateAccountCmd.getCurrentContext() >> Mock(CallContext) @@ -104,7 +104,7 @@ class LdapCreateAccountCmdSpec extends spock.lang.Specification { AccountService accountService = Mock(AccountService) def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService); when: "a user with an username, email, firstname and lastname is validated" - def result = ldapCreateAccountCmd.validateUser(new LdapUser("username","email","firstname","lastname","principal")) + def result = ldapCreateAccountCmd.validateUser(new LdapUser("username","email","firstname","lastname","principal","domain")) then: "the result is true" result == true } @@ -115,7 +115,7 @@ class LdapCreateAccountCmdSpec extends spock.lang.Specification { AccountService accountService = Mock(AccountService) def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService) when: "A user with no email address attempts to validate" - ldapCreateAccountCmd.validateUser(new LdapUser("username",null,"firstname","lastname","principal")) + ldapCreateAccountCmd.validateUser(new LdapUser("username",null,"firstname","lastname","principal","domain")) then: "An exception is thrown" thrown Exception } @@ -137,7 +137,7 @@ class LdapCreateAccountCmdSpec extends spock.lang.Specification { AccountService accountService = Mock(AccountService) def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService) when: "A user with no lastname attempts to validate" - ldapCreateAccountCmd.validateUser(new LdapUser("username","email","firstname",null,"principal")) + ldapCreateAccountCmd.validateUser(new LdapUser("username","email","firstname",null,"principal","domain")) then: "An exception is thown" thrown Exception } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy index d04b0940c2b..04556401d71 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy @@ -34,7 +34,8 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { given: "We have an LdapManager, DomainService and a LdapImportUsersCmd" def ldapManager = Mock(LdapManager) def domainService = Mock(DomainService) - def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService) + def accountService = Mock(AccountService) + def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService, accountService) when: "Get command name is called" String commandName = ldapImportUsersCmd.getCommandName() then: "ldapuserresponse is returned" @@ -42,7 +43,7 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { } def "Test successful response from execute"() { - given: "We have an LdapManager, DomainService, one user and a LdapImportUsersCmd" + given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" def ldapManager = Mock(LdapManager) def domainService = Mock(DomainService) def accountService = Mock(AccountService) @@ -68,4 +69,123 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { then: "a list of size 2 is returned" ldapImportUsersCmd.responseObject.getResponses().size() == 2 } + + def "Test successful response from execute with group specified"() { + given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" + def ldapManager = Mock(LdapManager) + def domainService = Mock(DomainService) + def accountService = Mock(AccountService) + + List users = new ArrayList() + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) + ldapManager.getUsersInGroup("TestGroup") >> users + LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") + LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") + ldapManager.createLdapUserResponse(_) >>>[response1, response2] + + + Domain domain = new DomainVO("TestGroup", 1L, 1L, "TestGroup", UUID.randomUUID().toString()) + domainService.getDomainByName("TestGroup", 1L) >>> [null, domain] + 1 * domainService.createDomain("TestGroup", 1L, "TestGroup", _) >> domain + + def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService, accountService) + ldapImportUsersCmd.accountType = 2; + ldapImportUsersCmd.groupName = "TestGroup"; + + when: "LdapListUsersCmd is executed" + ldapImportUsersCmd.execute() + then: "a list of size 2 is returned" + ldapImportUsersCmd.responseObject.getResponses().size() == 2 + } + + def "Test successful response from execute with group and domain specified"() { + given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" + def ldapManager = Mock(LdapManager) + def domainService = Mock(DomainService) + def accountService = Mock(AccountService) + + List users = new ArrayList() + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) + ldapManager.getUsersInGroup("TestGroup") >> users + LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") + LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") + ldapManager.createLdapUserResponse(_) >>>[response1, response2] + + + Domain domain = new DomainVO("TestDomain", 1L, 1L, "TestDomain", UUID.randomUUID().toString()) + domainService.getDomainByName("TestDomain", 1L) >>> [null, domain] + 1 * domainService.createDomain("TestDomain", 1L, "TestDomain", _) >> domain + + def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService, accountService) + ldapImportUsersCmd.accountType = 2; + ldapImportUsersCmd.groupName = "TestGroup"; + ldapImportUsersCmd.domainName = "TestDomain"; + + when: "LdapListUsersCmd is executed" + ldapImportUsersCmd.execute() + then: "a list of size 2 is returned" + ldapImportUsersCmd.responseObject.getResponses().size() == 2 + } + + def "Test successful response from execute with domain specified"() { + given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" + def ldapManager = Mock(LdapManager) + def domainService = Mock(DomainService) + def accountService = Mock(AccountService) + + List users = new ArrayList() + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) + ldapManager.getUsers() >> users + LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") + LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") + ldapManager.createLdapUserResponse(_) >>>[response1, response2] + + + Domain domain = new DomainVO("TestDomain", 1L, 1L, "TestDomain", UUID.randomUUID().toString()) + domainService.getDomainByName("TestDomain", 1L) >>> [null, domain] + 1 * domainService.createDomain("TestDomain", 1L, "TestDomain", _) >> domain + + def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService, accountService) + ldapImportUsersCmd.accountType = 2; + ldapImportUsersCmd.domainName = "TestDomain"; + + when: "LdapListUsersCmd is executed" + ldapImportUsersCmd.execute() + then: "a list of size 2 is returned" + ldapImportUsersCmd.responseObject.getResponses().size() == 2 + } + + def "Test getDomain with no domain or group name specified specified"() { + given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" + def ldapManager = Mock(LdapManager) + def domainService = Mock(DomainService) + def accountService = Mock(AccountService) + def ldapImportUsersCmd = new LdapImportUsersCmd(ldapManager, domainService, accountService) + ldapImportUsersCmd.domainName = varDomainName + ldapImportUsersCmd.groupName = varGroupName + + def ldapUser1 = new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") + def ldapUser2 = new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering"); + + Domain domain = new DomainVO(expectedDomainName, 1L, 1L, expectedDomainName, UUID.randomUUID().toString()) + 2 * domainService.getDomainByName(expectedDomainName, 1L) >>> [null, domain] + 1 * domainService.createDomain(expectedDomainName, 1L, expectedDomainName, _) >> domain + + def result1 = ldapImportUsersCmd.getDomain(ldapUser1) + def result2 = ldapImportUsersCmd.getDomain(ldapUser2) + expect: "engineering domain is returned" + result1 == domain + result2 == domain + where: "The domain and group are set to the following values" + varDomainName | varGroupName | expectedDomainName + null | null | "engineering" + "TestDomain" | null | "TestDomain" + "TestDomain" | "TestGroup" | "TestDomain" + null | "TestGroup" | "TestGroup" + + } + } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy index 321e1af2ab4..42988e0caef 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy @@ -356,4 +356,20 @@ class LdapManagerImplSpec extends spock.lang.Specification { then: "true is returned because a configuration was found" result == true; } + + def "Test success getUsersInGroup"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext() >> null + List users = new ArrayList<>(); + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", "engineering")) + ldapUserManager.getUsersInGroup("engineering", _) >> users; + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for a group of users" + def result = ldapManager.getUsersInGroup("engineering") + then: "A list greater of size one is returned" + result.size() == 1; + } } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerSpec.groovy index 339923e57c0..fa735d3f67e 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerSpec.groovy @@ -21,8 +21,10 @@ import org.apache.cloudstack.ldap.LdapUserManager import spock.lang.Shared import javax.naming.NamingException +import javax.naming.NamingEnumeration import javax.naming.directory.Attribute import javax.naming.directory.Attributes +import javax.naming.directory.DirContext import javax.naming.directory.SearchControls import javax.naming.directory.SearchResult import javax.naming.ldap.LdapContext @@ -47,6 +49,38 @@ class LdapUserManagerSpec extends spock.lang.Specification { @Shared private def principal + private def createGroupSearchContext() { + + def umSearchResult = Mock(SearchResult) + umSearchResult.getName() >> principal; + umSearchResult.getAttributes() >> principal + + def uniqueMembers = new BasicNamingEnumerationImpl() + uniqueMembers.add(umSearchResult); + def attributes = Mock(Attributes) + def uniqueMemberAttribute = Mock(Attribute) + uniqueMemberAttribute.getId() >> "uniquemember" + uniqueMemberAttribute.getAll() >> uniqueMembers + attributes.get("uniquemember") >> uniqueMemberAttribute + + def groupSearchResult = Mock(SearchResult) + groupSearchResult.getName() >> principal; + groupSearchResult.getAttributes() >> attributes + + def searchGroupResults = new BasicNamingEnumerationImpl() + searchGroupResults.add(groupSearchResult); + + attributes = createUserAttributes(username, email, firstname, lastname) + SearchResult userSearchResult = createSearchResult(attributes) + def searchUsersResults = new BasicNamingEnumerationImpl() + searchUsersResults.add(userSearchResult); + + def context = Mock(LdapContext) + context.search(_, _, _) >>> [searchGroupResults, searchUsersResults]; + + return context + } + private def createContext() { Attributes attributes = createUserAttributes(username, email, firstname, lastname) SearchResult searchResults = createSearchResult(attributes) @@ -65,6 +99,7 @@ class LdapUserManagerSpec extends spock.lang.Specification { search.getName() >> "cn=" + attributes.getAt("uid").get(); search.getAttributes() >> attributes + search.getNameInNamespace() >> principal return search } @@ -105,6 +140,9 @@ class LdapUserManagerSpec extends spock.lang.Specification { ldapConfiguration.getFirstnameAttribute() >> "givenname" ldapConfiguration.getLastnameAttribute() >> "sn" ldapConfiguration.getBaseDn() >> "dc=cloudstack,dc=org" + ldapConfiguration.getCommonNameAttribute() >> "cn" + ldapConfiguration.getGroupObject() >> "groupOfUniqueNames" + ldapConfiguration.getGroupUniqueMemeberAttribute() >> "uniquemember" username = "rmurphy" email = "rmurphy@test.com" @@ -203,4 +241,43 @@ class LdapUserManagerSpec extends spock.lang.Specification { expect: "The result is not null" result != null } + + def "test successful generateGroupSearchFilter"() { + given: "ldap user manager and ldap config" + def ldapUserManager = new LdapUserManager(ldapConfiguration) + def groupName = varGroupName == null ? "*" : varGroupName + def expectedResult = "(&(objectClass=groupOfUniqueNames)(cn="+groupName+"))"; + + def result = ldapUserManager.generateGroupSearchFilter(varGroupName) + expect: + result == expectedResult + where: "The group name passed is set to " + varGroupName << ["", null, "Murphy"] + } + + def "test successful getUsersInGroup"(){ + given: "ldap user manager and ldap config" + def ldapUserManager = new LdapUserManager(ldapConfiguration) + + when: "A request for users is made" + def result = ldapUserManager.getUsersInGroup("engineering", createGroupSearchContext()) + then: "one user is returned" + result.size() == 1 + } + + def "test successful getUserForDn"(){ + given: "ldap user manager and ldap config" + def ldapUserManager = new LdapUserManager(ldapConfiguration) + + when: "A request for users is made" + def result = ldapUserManager.getUserForDn("cn=Ryan Murphy,ou=engineering,dc=cloudstack,dc=org",createContext()) + then: "A list of users is returned" + result != 1 + result.username == username + result.email == email + result.firstname == firstname + result.lastname == lastname + result.principal == principal + + } } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 3fdc3439092..e78757639ee 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -394,6 +394,9 @@ public enum Config { LdapSearchGroupPrinciple("Advanced", ManagementServer.class, String.class, "ldap.search.group.principle", null, "Sets the principle of the group that users must be a member of", null), LdapTrustStore("Advanced", ManagementServer.class, String.class, "ldap.truststore", null, "Sets the path to the truststore to use for SSL", null), LdapTrustStorePassword("Advanced", ManagementServer.class, String.class, "ldap.truststore.password", null, "Sets the password for the truststore", null), + LdapGroupObject("Advanced", ManagementServer.class, String.class, "ldap.group.object", "groupOfUniqueNames", "Sets the object type of groups within LDAP", null), + LdapGroupUniqueMemberAttribute("Advanced", ManagementServer.class, String.class, "ldap.group.user.uniquemember", "uniquemember", + "Sets the attribute for uniquemembers within a group", null), // VMSnapshots VMSnapshotMax("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.max", "10", "Maximum vm snapshots for a vm", null), diff --git a/setup/db/db/schema-421to430.sql b/setup/db/db/schema-421to430.sql index 0de9dfd9a74..1e803dc6eb6 100644 --- a/setup/db/db/schema-421to430.sql +++ b/setup/db/db/schema-421to430.sql @@ -483,3 +483,8 @@ ALTER TABLE `cloud`.`nic_details` CHANGE `display_detail` `display` tinyint(1) N ALTER TABLE `cloud`.`user_vm_details` CHANGE `display_detail` `display` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'True if the detail can be displayed to the end user'; ALTER TABLE `cloud`.`service_offering_details` ADD COLUMN `display` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'True if the detail can be displayed to the end user'; ALTER TABLE `cloud`.`storage_pool_details` ADD COLUMN `display` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'True if the detail can be displayed to the end user'; + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.group.object', 'groupOfUniqueNames', +'Sets the object type of groups within LDAP','groupOfUniqueNames',NULL,NULL,0); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.group.user.uniquemember', 'uniquemember', +'Sets the attribute for uniquemembers within a group','uniquemember',NULL,NULL,0);