diff --git a/.gitignore b/.gitignore index 929227d8604..5efa7bbb5ca 100644 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,5 @@ debian/*.debhelper replace.properties.tmp build-indep-stamp configure-stamp -*_flymake.js \ No newline at end of file +*_flymake.js +engine/storage/integration-test/test-output diff --git a/api/src/com/cloud/configuration/ConfigurationService.java b/api/src/com/cloud/configuration/ConfigurationService.java index 9e5fa82f1a4..d3dc90c20da 100644 --- a/api/src/com/cloud/configuration/ConfigurationService.java +++ b/api/src/com/cloud/configuration/ConfigurationService.java @@ -21,8 +21,6 @@ import java.util.List; import javax.naming.NamingException; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; @@ -261,12 +259,6 @@ public interface ConfigurationService { Long getDefaultPageSize(); - boolean updateLDAP(LDAPConfigCmd cmd) throws NamingException; - - boolean removeLDAP(LDAPRemoveCmd cmd); - - LDAPConfigCmd listLDAPConfig(LDAPConfigCmd cmd); - PortableIpRange createPortableIpRange(CreatePortableIpRangeCmd cmd) throws ConcurrentOperationException; boolean deletePortableIpRange(DeletePortableIpRangeCmd cmd); diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 00d526d216e..f85784bbde0 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -525,15 +525,4 @@ public class ApiConstants { public enum VMDetails { all, group, nics, stats, secgrp, tmpl, servoff, iso, volume, min, affgrp; } - - public enum LDAPParams { - hostname, port, usessl, queryfilter, searchbase, dn, passwd, truststore, truststorepass; - - @Override - public String toString() { - return "ldap." + name(); - } - } - - } diff --git a/api/src/org/apache/cloudstack/api/LdapValidator.java b/api/src/org/apache/cloudstack/api/LdapValidator.java new file mode 100644 index 00000000000..4619a588b43 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/LdapValidator.java @@ -0,0 +1,5 @@ +package org.apache.cloudstack.api; + +public interface LdapValidator { + boolean isLdapEnabled(); +} diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index ae02478f127..a8de31d8c2d 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -60,7 +60,6 @@ import org.apache.cloudstack.api.response.IpForwardingRuleResponse; import org.apache.cloudstack.api.response.IsolationMethodResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse; import org.apache.cloudstack.api.response.LBStickinessResponse; -import org.apache.cloudstack.api.response.LDAPConfigResponse; import org.apache.cloudstack.api.response.LoadBalancerResponse; import org.apache.cloudstack.api.response.NetworkACLItemResponse; import org.apache.cloudstack.api.response.NetworkACLResponse; @@ -354,8 +353,6 @@ public interface ResponseGenerator { VirtualRouterProviderResponse createVirtualRouterProviderResponse(VirtualRouterProvider result); - LDAPConfigResponse createLDAPConfigResponse(String hostname, Integer port, Boolean useSSL, String queryFilter, String baseSearch, String dn); - StorageNetworkIpRangeResponse createStorageNetworkIpRangeResponse(StorageNetworkIpRange result); RegionResponse createRegionResponse(Region region); diff --git a/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPConfigCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPConfigCmd.java deleted file mode 100644 index 38f58eca5f0..00000000000 --- a/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPConfigCmd.java +++ /dev/null @@ -1,205 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.ldap; - - -import java.util.ArrayList; -import java.util.List; - -import javax.naming.NamingException; - -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.LDAPConfigResponse; -import org.apache.cloudstack.api.response.ListResponse; -import org.apache.log4j.Logger; - -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.user.Account; - -@APICommand(name = "ldapConfig", description="Configure the LDAP context for this site.", responseObject=LDAPConfigResponse.class, since="3.0.0") -public class LDAPConfigCmd extends BaseCmd { - public static final Logger s_logger = Logger.getLogger(LDAPConfigCmd.class.getName()); - - private static final String s_name = "ldapconfigresponse"; - - ///////////////////////////////////////////////////// - //////////////// API parameters ///////////////////// - ///////////////////////////////////////////////////// - @Parameter(name=ApiConstants.LIST_ALL, type=CommandType.BOOLEAN, description="If true return current LDAP configuration") - private Boolean listAll; - - @Parameter(name=ApiConstants.HOST_NAME, type=CommandType.STRING, description="Hostname or ip address of the ldap server eg: my.ldap.com") - private String hostname; - - @Parameter(name=ApiConstants.PORT, type=CommandType.INTEGER, description="Specify the LDAP port if required, default is 389.") - private Integer port=0; - - @Parameter(name=ApiConstants.USE_SSL, type=CommandType.BOOLEAN, description="Check Use SSL if the external LDAP server is configured for LDAP over SSL.") - private Boolean useSSL; - - @Parameter(name=ApiConstants.SEARCH_BASE, type=CommandType.STRING, description="The search base defines the starting point for the search in the directory tree Example: dc=cloud,dc=com.") - private String searchBase; - - @Parameter(name=ApiConstants.QUERY_FILTER, type=CommandType.STRING, description="You specify a query filter here, which narrows down the users, who can be part of this domain.") - private String queryFilter; - - @Parameter(name=ApiConstants.BIND_DN, type=CommandType.STRING, description="Specify the distinguished name of a user with the search permission on the directory.") - private String bindDN; - - @Parameter(name=ApiConstants.BIND_PASSWORD, type=CommandType.STRING, description="Enter the password.") - private String bindPassword; - - @Parameter(name=ApiConstants.TRUST_STORE, type=CommandType.STRING, description="Enter the path to trust certificates store.") - private String trustStore; - - @Parameter(name=ApiConstants.TRUST_STORE_PASSWORD, type=CommandType.STRING, description="Enter the password for trust store.") - private String trustStorePassword; - - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// - - public Boolean getListAll() { - return listAll == null ? Boolean.FALSE : listAll; - } - - public String getBindPassword() { - return bindPassword; - } - - public String getBindDN() { - return bindDN; - } - - public void setBindDN(String bdn) { - this.bindDN=bdn; - } - - public String getQueryFilter() { - return queryFilter; - } - - public void setQueryFilter(String queryFilter) { - this.queryFilter=queryFilter; - } - public String getSearchBase() { - return searchBase; - } - - public void setSearchBase(String searchBase) { - this.searchBase=searchBase; - } - - public Boolean getUseSSL() { - return useSSL == null ? Boolean.FALSE : useSSL; - } - - public void setUseSSL(Boolean useSSL) { - this.useSSL=useSSL; - } - - public String getHostname() { - return hostname; - } - - public void setHostname(String hostname) { - this.hostname=hostname; - } - - public Integer getPort() { - return port <= 0 ? 389 : port; - } - - public void setPort(Integer port) { - this.port=port; - } - - public String getTrustStore() { - return trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore=trustStore; - } - - public String getTrustStorePassword() { - return trustStorePassword; - } - - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - - @Override - public void execute() throws ResourceUnavailableException, - InsufficientCapacityException, ServerApiException, - ConcurrentOperationException, ResourceAllocationException { - try { - if (getListAll()){ - // return the existing conf - LDAPConfigCmd cmd = _configService.listLDAPConfig(this); - ListResponse response = new ListResponse(); - List responses = new ArrayList(); - - if(!cmd.getHostname().equals("")) { - responses.add(_responseGenerator.createLDAPConfigResponse(cmd.getHostname(), cmd.getPort(), cmd.getUseSSL(), cmd.getQueryFilter(), cmd.getSearchBase(), cmd.getBindDN())); - } - - response.setResponses(responses); - response.setResponseName(getCommandName()); - this.setResponseObject(response); - } - else if (getHostname()==null || getSearchBase() == null || getQueryFilter() == null) { - throw new InvalidParameterValueException("You need to provide hostname, searchbase and queryfilter to configure your LDAP server"); - } - else { - boolean result = _configService.updateLDAP(this); - if (result){ - LDAPConfigResponse lr = _responseGenerator.createLDAPConfigResponse(getHostname(), getPort(), getUseSSL(), getQueryFilter(), getSearchBase(), getBindDN()); - lr.setResponseName(getCommandName()); - this.setResponseObject(lr); - } - } - } - catch (NamingException ne){ - ne.printStackTrace(); - } - - } - - @Override - public String getCommandName() { - return s_name; - } - - @Override - public long getEntityOwnerId() { - return Account.ACCOUNT_ID_SYSTEM; - } - - -} \ No newline at end of file diff --git a/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPRemoveCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPRemoveCmd.java deleted file mode 100644 index 5159fbadb0b..00000000000 --- a/api/src/org/apache/cloudstack/api/command/admin/ldap/LDAPRemoveCmd.java +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.ldap; - - -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.response.LDAPConfigResponse; -import org.apache.cloudstack.api.response.LDAPRemoveResponse; -import org.apache.log4j.Logger; - -import com.cloud.user.Account; - -@APICommand(name = "ldapRemove", description="Remove the LDAP context for this site.", responseObject=LDAPConfigResponse.class, since="3.0.1") -public class LDAPRemoveCmd extends BaseCmd { - public static final Logger s_logger = Logger.getLogger(LDAPRemoveCmd.class.getName()); - - private static final String s_name = "ldapremoveresponse"; - - ///////////////////////////////////////////////////// - //////////////// API parameters ///////////////////// - ///////////////////////////////////////////////////// - - - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// - - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - - @Override - public void execute(){ - boolean result = _configService.removeLDAP(this); - if (result){ - LDAPRemoveResponse lr = new LDAPRemoveResponse(); - lr.setObjectName("ldapremove"); - lr.setResponseName(getCommandName()); - this.setResponseObject(lr); - } - } - - @Override - public String getCommandName() { - return s_name; - } - - @Override - public long getEntityOwnerId() { - return Account.ACCOUNT_ID_SYSTEM; - } - - -} diff --git a/api/src/org/apache/cloudstack/api/response/LDAPConfigResponse.java b/api/src/org/apache/cloudstack/api/response/LDAPConfigResponse.java deleted file mode 100644 index bbeec630d81..00000000000 --- a/api/src/org/apache/cloudstack/api/response/LDAPConfigResponse.java +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; - -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; - -public class LDAPConfigResponse extends BaseResponse { - - @SerializedName(ApiConstants.HOST_NAME) @Param(description="Hostname or ip address of the ldap server eg: my.ldap.com") - private String hostname; - - @SerializedName(ApiConstants.PORT) @Param(description="Specify the LDAP port if required, default is 389") - private String port; - - @SerializedName(ApiConstants.USE_SSL) @Param(description="Check Use SSL if the external LDAP server is configured for LDAP over SSL") - private String useSSL; - - @SerializedName(ApiConstants.SEARCH_BASE) @Param(description="The search base defines the starting point for the search in the directory tree Example: dc=cloud,dc=com") - private String searchBase; - - @SerializedName(ApiConstants.QUERY_FILTER) @Param(description="You specify a query filter here, which narrows down the users, who can be part of this domain") - private String queryFilter; - - @SerializedName(ApiConstants.BIND_DN) @Param(description="Specify the distinguished name of a user with the search permission on the directory") - private String bindDN; - - @SerializedName(ApiConstants.BIND_PASSWORD) @Param(description="DN password") - private String bindPassword; - - public String getHostname() { - return hostname; - } - - public void setHostname(String hostname) { - this.hostname = hostname; - } - - public String getPort() { - return port; - } - - public void setPort(String port) { - this.port = port; - } - - public String getUseSSL() { - return useSSL; - } - - public void setUseSSL(String useSSL) { - this.useSSL = useSSL; - } - - public String getSearchBase() { - return searchBase; - } - - public void setSearchBase(String searchBase) { - this.searchBase = searchBase; - } - - public String getQueryFilter() { - return queryFilter; - } - - public void setQueryFilter(String queryFilter) { - this.queryFilter = queryFilter; - } - - public String getBindDN() { - return bindDN; - } - - public void setBindDN(String bindDN) { - this.bindDN = bindDN; - } - - public String getBindPassword() { - return bindPassword; - } - - public void setBindPassword(String bindPassword) { - this.bindPassword = bindPassword; - } - - -} diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index 4fc09eedf98..bc1e43692a3 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -43,6 +43,7 @@ message.action.delete.nic=Please confirm that want to remove this NIC, which wil changed.item.properties=Changed item properties confirm.enable.s3=Please fill in the following information to enable support for S3-backed Secondary Storage confirm.enable.swift=Please fill in the following information to enable support for Swift +error.could.not.change.your.password.because.ldap.is.enabled=Error cloud not change your password because LDAP is enabled. error.could.not.enable.zone=Could not enable zone error.installWizard.message=Something went wrong; you may go back and correct any errors error.invalid.username.password=Invalid username or password diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 956c77c5fa6..708555a175f 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -387,7 +387,7 @@ - + @@ -396,6 +396,11 @@ + + + + + - diff --git a/docs/en-US/example-activedirectory-configuration.xml b/docs/en-US/example-activedirectory-configuration.xml index 98ab5dae681..5a8178d5843 100644 --- a/docs/en-US/example-activedirectory-configuration.xml +++ b/docs/en-US/example-activedirectory-configuration.xml @@ -24,14 +24,14 @@
Example LDAP Configuration for Active Directory - This shows the configuration settings required for using ActiveDirectory + This shows the configuration settings required for using ActiveDirectory. samAccountName - Logon name mail - Email Address cn - Real name Along with this the ldap.user.object name needs to be modified, by default ActiveDirectory uses the value "user" for this. - Map the following attributes accordingly as shown below within the cloudstack ldap configuration: + Map the following attributes accordingly as shown below: diff --git a/docs/en-US/example-openldap-configuration.xml b/docs/en-US/example-openldap-configuration.xml index 2b28032dfe4..aa57a00cf18 100644 --- a/docs/en-US/example-openldap-configuration.xml +++ b/docs/en-US/example-openldap-configuration.xml @@ -24,8 +24,8 @@
Example LDAP Configuration for OpenLdap - This shows the configuration settings required for using OpenLDAP - The default values supplied are suited for OpenLDAP + This shows the configuration settings required for using OpenLDAP. + The default values supplied are suited for OpenLDAP. uid - Logon name mail - Email Address diff --git a/docs/en-US/images/add-ldap-configuration-ad.png b/docs/en-US/images/add-ldap-configuration-ad.png index 7d0aa0f44f9..d4d3e789b29 100644 Binary files a/docs/en-US/images/add-ldap-configuration-ad.png and b/docs/en-US/images/add-ldap-configuration-ad.png differ diff --git a/docs/en-US/images/add-ldap-configuration-openldap.png b/docs/en-US/images/add-ldap-configuration-openldap.png index c801275dbc9..70ce579f87c 100644 Binary files a/docs/en-US/images/add-ldap-configuration-openldap.png and b/docs/en-US/images/add-ldap-configuration-openldap.png differ diff --git a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java index e257c708bed..1e32e845496 100644 --- a/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java +++ b/plugins/api/rate-limit/src/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -30,7 +30,6 @@ import net.sf.ehcache.CacheManager; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.APIChecker; -import org.apache.cloudstack.api.ApiConstants.LDAPParams; import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd; import org.apache.cloudstack.api.command.user.ratelimit.GetApiLimitCmd; import org.apache.cloudstack.api.response.ApiLimitResponse; diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index 02752ac3f4b..1f9dea06631 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -1,22 +1,14 @@ - - + + 4.0.0 cloud-plugin-user-authenticator-ldap Apache CloudStack Plugin - User Authenticator LDAP @@ -26,4 +18,93 @@ 4.3.0-SNAPSHOT ../../pom.xml + + + + + org.codehaus.gmaven + gmaven-plugin + 1.3 + + 1.7 + + + + + compile + testCompile + + + + + test/groovy + + **/*.groovy + + + + + + + + + org.codehaus.gmaven.runtime + gmaven-runtime-1.7 + 1.3 + + + org.codehaus.groovy + groovy-all + + + + + org.codehaus.groovy + groovy-all + 2.0.5 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Spec* + + + + + + com.btmatthews.maven.plugins + ldap-maven-plugin + 1.1.0 + + 11389 + ldap + false + dc=cloudstack,dc=org + 10389 + test/resources/cloudstack.org.ldif + + + + + + + + + + org.spockframework + spock-core + 0.7-groovy-2.0 + + + + + cglib + cglib-nodep + 2.2 + + diff --git a/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java b/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java deleted file mode 100644 index 900276083c3..00000000000 --- a/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java +++ /dev/null @@ -1,174 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -package com.cloud.server.auth; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Hashtable; -import java.util.Map; - -import javax.ejb.Local; -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import javax.naming.Context; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; - -import org.apache.cloudstack.api.ApiConstants.LDAPParams; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; - -import org.apache.log4j.Logger; -import org.bouncycastle.util.encoders.Base64; - -import com.cloud.user.UserAccount; -import com.cloud.user.dao.UserAccountDao; -import com.cloud.utils.exception.CloudRuntimeException; - -@Local(value={UserAuthenticator.class}) -public class LDAPUserAuthenticator extends DefaultUserAuthenticator { - public static final Logger s_logger = Logger.getLogger(LDAPUserAuthenticator.class); - - @Inject private ConfigurationDao _configDao; - @Inject private UserAccountDao _userAccountDao; - - @Override - public boolean authenticate(String username, String password, Long domainId, Map 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; - } - - String url = _configDao.getValue(LDAPParams.hostname.toString()); - if (url==null){ - s_logger.debug("LDAP authenticator is not configured."); - return false; - } - String port = _configDao.getValue(LDAPParams.port.toString()); - String queryFilter = _configDao.getValue(LDAPParams.queryfilter.toString()); - String searchBase = _configDao.getValue(LDAPParams.searchbase.toString()); - Boolean useSSL = Boolean.valueOf(_configDao.getValue(LDAPParams.usessl.toString())); - String bindDN = _configDao.getValue(LDAPParams.dn.toString()); - String bindPasswd = _configDao.getValue(LDAPParams.passwd.toString()); - String trustStore = _configDao.getValue(LDAPParams.truststore.toString()); - String trustStorePassword = _configDao.getValue(LDAPParams.truststorepass.toString()); - - try { - // get all params - Hashtable env = new Hashtable(11); - env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"); - String protocol = "ldap://" ; - if (useSSL){ - env.put(Context.SECURITY_PROTOCOL, "ssl"); - protocol="ldaps://" ; - System.setProperty("javax.net.ssl.trustStore", trustStore); - System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); - } - env.put(Context.PROVIDER_URL, protocol + url + ":" + port); - - if (bindDN != null && bindPasswd != null){ - env.put(Context.SECURITY_PRINCIPAL, bindDN); - env.put(Context.SECURITY_CREDENTIALS, bindPasswd); - } - else { - // Use anonymous authentication - env.put(Context.SECURITY_AUTHENTICATION, "none"); - } - // Create the initial context - DirContext ctx = new InitialDirContext(env); - // use this context to search - - // substitute the queryFilter with this user info - queryFilter = queryFilter.replaceAll("\\%u", username); - queryFilter = queryFilter.replaceAll("\\%n", user.getFirstname() + " " + user.getLastname()); - queryFilter = queryFilter.replaceAll("\\%e", user.getEmail()); - - - SearchControls sc = new SearchControls(); - String[] searchFilter = { "dn" }; - sc.setReturningAttributes(new String[0]); //return no attributes - sc.setReturningAttributes(searchFilter); - sc.setSearchScope(SearchControls.SUBTREE_SCOPE); - sc.setCountLimit(1); - - // Search for objects with those matching attributes - NamingEnumeration answer = ctx.search(searchBase, queryFilter, sc); - SearchResult sr = answer.next(); - String cn = sr.getName(); - answer.close(); - ctx.close(); - - s_logger.info("DN from LDAP =" + cn); - - // check the password - env = new Hashtable(11); - env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"); - protocol = "ldap://" ; - if (useSSL){ - env.put(Context.SECURITY_PROTOCOL, "ssl"); - protocol="ldaps://" ; - } - env.put(Context.PROVIDER_URL, protocol + url + ":" + port); - env.put(Context.SECURITY_PRINCIPAL, cn + "," + searchBase); - env.put(Context.SECURITY_CREDENTIALS, password); - // Create the initial context - ctx = new InitialDirContext(env); - ctx.close(); - - } catch (NamingException ne) { - s_logger.warn("Authentication Failed ! " + ne.getMessage() + (ne.getCause() != null ? ("; Caused by:" + ne.getCause().getMessage()) : "")); - return false; - } - catch (Exception e){ - e.printStackTrace(); - s_logger.warn("Unknown error encountered " + e.getMessage()); - return false; - } - - // authenticate - return true; - } - - @Override - public boolean configure(String name, Map params) - throws ConfigurationException { - if (name == null) { - name = "LDAP"; - } - super.configure(name, params); - return true; - } - - @Override - public String encode(String password) { - // Password is not used, so set to a random string - try { - SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG"); - byte bytes[] = new byte[20]; - randomGen.nextBytes(bytes); - return Base64.encode(bytes).toString(); - } catch (NoSuchAlgorithmException e) { - throw new CloudRuntimeException("Failed to generate random password",e); - } - } -} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapAddConfigurationCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapAddConfigurationCmd.java new file mode 100644 index 00000000000..d5ccdafeaa9 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapAddConfigurationCmd.java @@ -0,0 +1,82 @@ +package org.apache.cloudstack.api.command; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.LdapConfigurationResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.log4j.Logger; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; + +@APICommand(name = "addLdapConfiguration", description = "Add a new Ldap Configuration", responseObject = LdapConfigurationResponse.class, since = "4.2.0") +public class LdapAddConfigurationCmd extends BaseCmd { + public static final Logger s_logger = Logger + .getLogger(LdapAddConfigurationCmd.class.getName()); + private static final String s_name = "ldapconfigurationresponse"; + + @Inject + private LdapManager _ldapManager; + + @Parameter(name = "hostname", type = CommandType.STRING, required = true, description = "Hostname") + private String hostname; + + @Parameter(name = "port", type = CommandType.INTEGER, required = true, description = "Port") + private int port; + + public LdapAddConfigurationCmd() { + super(); + } + + public LdapAddConfigurationCmd(final LdapManager ldapManager) { + super(); + _ldapManager = ldapManager; + } + + @Override + public void execute() throws ServerApiException { + try { + final LdapConfigurationResponse response = _ldapManager + .addConfiguration(hostname, port); + response.setObjectName("LdapAddConfiguration"); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (final InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + e.toString()); + } + + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public void setHostname(final String hostname) { + this.hostname = hostname; + } + + public void setPort(final int port) { + this.port = port; + } + +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapCreateAccountCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapCreateAccountCmd.java new file mode 100644 index 00000000000..981e72e64e1 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapCreateAccountCmd.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.NamingException; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; + +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.UserAccount; + +@APICommand(name = "ldapCreateAccount", description = "Creates an account from an LDAP user", responseObject = AccountResponse.class, since = "4.2.0") +public class LdapCreateAccountCmd extends BaseCmd { + public static final Logger s_logger = Logger + .getLogger(LdapCreateAccountCmd.class.getName()); + private static final String s_name = "createaccountresponse"; + + @Inject + private LdapManager _ldapManager; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "Creates the user under the specified account. If no account is specified, the username will be used as the account name.") + private String accountName; + + @Parameter(name = ApiConstants.ACCOUNT_TYPE, type = CommandType.SHORT, required = true, description = "Type of the account. Specify 0 for user, 1 for root admin, and 2 for domain admin") + private Short accountType; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "Creates the user under the specified domain.") + private Long domainId; + + @Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, description = "Specifies a timezone for this command. For more information on the timezone parameter, see Time Zone Format.") + private String timezone; + + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Unique username.") + private String username; + + @Parameter(name = ApiConstants.NETWORK_DOMAIN, type = CommandType.STRING, description = "Network domain for the account's networks") + private String networkDomain; + + @Parameter(name = ApiConstants.ACCOUNT_DETAILS, type = CommandType.MAP, description = "details for account used to store specific parameters") + private Map details; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.STRING, description = "Account UUID, required for adding account from external provisioning system") + private String accountUUID; + + @Parameter(name = ApiConstants.USER_ID, type = CommandType.STRING, description = "User UUID, required for adding account from external provisioning system") + private String userUUID; + + public LdapCreateAccountCmd() { + super(); + } + + public LdapCreateAccountCmd(final LdapManager ldapManager, + final AccountService accountService) { + super(); + _ldapManager = ldapManager; + _accountService = accountService; + } + + UserAccount createCloudstackUserAccount(final LdapUser user) { + return _accountService.createUserAccount(username, generatePassword(), + user.getFirstname(), user.getLastname(), user.getEmail(), + timezone, accountName, accountType, domainId, networkDomain, + details, accountUUID, userUUID); + } + + @Override + public void execute() throws ServerApiException { + final CallContext callContext = getCurrentContext(); + callContext.setEventDetails("Account Name: " + accountName + + ", Domain Id:" + domainId); + try { + final LdapUser user = _ldapManager.getUser(username); + validateUser(user); + final UserAccount userAccount = createCloudstackUserAccount(user); + if (userAccount != null) { + final AccountResponse response = _responseGenerator + .createUserAccountResponse(userAccount); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create a user account"); + } + } catch (final NamingException e) { + throw new ServerApiException( + ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, + "No LDAP user exists with the username of " + username); + } + } + + private String generatePassword() throws ServerApiException { + try { + final SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG"); + final byte bytes[] = new byte[20]; + randomGen.nextBytes(bytes); + return Base64.encode(bytes).toString(); + } catch (final NoSuchAlgorithmException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to generate random password"); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + CallContext getCurrentContext() { + return CallContext.current(); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + private boolean validateUser(final LdapUser user) throws ServerApiException { + if (user.getEmail() == null) { + throw new ServerApiException( + ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, username + + " has no email address set within LDAP"); + } + if (user.getFirstname() == null) { + throw new ServerApiException( + ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, username + + " has no firstname set within LDAP"); + } + if (user.getLastname() == null) { + throw new ServerApiException( + ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, username + + " has no lastname set within LDAP"); + } + return true; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java new file mode 100644 index 00000000000..d57c9a39985 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.LdapConfigurationResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.log4j.Logger; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; + +@APICommand(name = "deleteLdapConfiguration", description = "Remove an Ldap Configuration", responseObject = LdapConfigurationResponse.class, since = "4.2.0") +public class LdapDeleteConfigurationCmd extends BaseCmd { + public static final Logger s_logger = Logger + .getLogger(LdapDeleteConfigurationCmd.class.getName()); + private static final String s_name = "ldapconfigurationresponse"; + + @Inject + private LdapManager _ldapManager; + + @Parameter(name = "hostname", type = CommandType.STRING, required = true, description = "Hostname") + private String hostname; + + public LdapDeleteConfigurationCmd() { + super(); + } + + public LdapDeleteConfigurationCmd(final LdapManager ldapManager) { + super(); + _ldapManager = ldapManager; + } + + @Override + public void execute() throws ServerApiException { + try { + final LdapConfigurationResponse response = _ldapManager + .deleteConfiguration(hostname); + response.setObjectName("LdapDeleteConfiguration"); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (final InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + e.toString()); + } + + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java new file mode 100644 index 00000000000..3d9323da6f9 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.LdapConfigurationResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.ldap.LdapConfigurationVO; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; +import com.cloud.utils.Pair; + +@APICommand(name = "listLdapConfigurations", responseObject = LdapConfigurationResponse.class, description = "Lists all LDAP configurations", since = "4.2.0") +public class LdapListConfigurationCmd extends BaseListCmd { + public static final Logger s_logger = Logger + .getLogger(LdapListConfigurationCmd.class.getName()); + + private static final String s_name = "ldapconfigurationresponse"; + + @Inject + private LdapManager _ldapManager; + + @Parameter(name = "hostname", type = CommandType.STRING, required = false, description = "Hostname") + private String hostname; + + @Parameter(name = "port", type = CommandType.INTEGER, required = false, description = "Port") + private int port; + + public LdapListConfigurationCmd() { + super(); + } + + public LdapListConfigurationCmd(final LdapManager ldapManager) { + super(); + _ldapManager = ldapManager; + } + + private List createLdapConfigurationResponses( + final List configurations) { + final List responses = new ArrayList(); + for (final LdapConfigurationVO resource : configurations) { + final LdapConfigurationResponse configurationResponse = _ldapManager + .createLdapConfigurationResponse(resource); + configurationResponse.setObjectName("LdapConfiguration"); + responses.add(configurationResponse); + } + return responses; + } + + @Override + public void execute() { + final Pair, Integer> result = _ldapManager + .listConfigurations(this); + final List responses = createLdapConfigurationResponses(result + .first()); + final ListResponse response = new ListResponse(); + response.setResponses(responses, result.second()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public void setHostname(final String hostname) { + this.hostname = hostname; + } + + public void setPort(final int port) { + this.port = port; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListUsersCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListUsersCmd.java new file mode 100644 index 00000000000..18e36f56425 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapListUsersCmd.java @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; +import org.apache.cloudstack.api.response.LdapUserResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException; +import org.apache.cloudstack.query.QueryService; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; + +@APICommand(name = "listLdapUsers", responseObject = LdapUserResponse.class, description = "Lists all LDAP Users", since = "4.2.0") +public class LdapListUsersCmd extends BaseListCmd { + + public static final Logger s_logger = Logger + .getLogger(LdapListUsersCmd.class.getName()); + private static final String s_name = "ldapuserresponse"; + @Inject + private LdapManager _ldapManager; + + @Inject + private QueryService _queryService; + + @Parameter(name = "listtype", type = CommandType.STRING, required = false, description = "Determines whether all ldap users are returned or just non-cloudstack users") + private String listType; + + public LdapListUsersCmd() { + super(); + } + + public LdapListUsersCmd(final LdapManager ldapManager, + final QueryService queryService) { + super(); + _ldapManager = ldapManager; + _queryService = queryService; + } + + private List createLdapUserResponse( + final List users) { + final List ldapResponses = new ArrayList(); + for (final LdapUser user : users) { + if (getListType().equals("all") || !isACloudstackUser(user)) { + final LdapUserResponse ldapResponse = _ldapManager + .createLdapUserResponse(user); + ldapResponse.setObjectName("LdapUser"); + ldapResponses.add(ldapResponse); + } + } + return ldapResponses; + } + + @Override + public void execute() throws ServerApiException { + List ldapResponses = null; + final ListResponse response = new ListResponse(); + try { + final List users = _ldapManager.getUsers(); + ldapResponses = createLdapUserResponse(users); + } catch (final NoLdapUserMatchingQueryException ex) { + ldapResponses = new ArrayList(); + } finally { + response.setResponses(ldapResponses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + private String getListType() { + return listType == null ? "all" : listType; + } + + private boolean isACloudstackUser(final LdapUser ldapUser) { + final ListResponse response = _queryService + .searchForUsers(new ListUsersCmd()); + final List cloudstackUsers = response.getResponses(); + if (cloudstackUsers != null && cloudstackUsers.size() != 0) { + for (final UserResponse cloudstackUser : response.getResponses()) { + if (ldapUser.getUsername().equals(cloudstackUser.getUsername())) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapUserSearchCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapUserSearchCmd.java new file mode 100644 index 00000000000..e72371c113e --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapUserSearchCmd.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.LdapUserResponse; +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.log4j.Logger; + +import com.cloud.user.Account; + +@APICommand(name = "searchLdap", responseObject = LdapUserResponse.class, description = "Searches LDAP based on the username attribute", since = "4.2.0") +public class LdapUserSearchCmd extends BaseListCmd { + + public static final Logger s_logger = Logger + .getLogger(LdapUserSearchCmd.class.getName()); + private static final String s_name = "ldapuserresponse"; + @Inject + private LdapManager _ldapManager; + + @Parameter(name = "query", type = CommandType.STRING, entityType = LdapUserResponse.class, required = true, description = "query to search using") + private String query; + + public LdapUserSearchCmd() { + super(); + } + + public LdapUserSearchCmd(final LdapManager ldapManager) { + super(); + _ldapManager = ldapManager; + } + + private List createLdapUserResponse( + final List users) { + final List ldapUserResponses = new ArrayList(); + if (users != null) { + for (final LdapUser user : users) { + final LdapUserResponse ldapUserResponse = _ldapManager + .createLdapUserResponse(user); + ldapUserResponse.setObjectName("LdapUser"); + ldapUserResponses.add(ldapUserResponse); + } + } + return ldapUserResponses; + } + + @Override + public void execute() { + final ListResponse response = new ListResponse(); + List users = null; + + try { + users = _ldapManager.searchUsers(query); + } catch (final NoLdapUserMatchingQueryException e) { + s_logger.debug(e.getMessage()); + } + + final List ldapUserResponses = createLdapUserResponse(users); + + response.setResponses(ldapUserResponses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapConfigurationResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapConfigurationResponse.java new file mode 100644 index 00000000000..39d5baeae84 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapConfigurationResponse.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class LdapConfigurationResponse extends BaseResponse { + @SerializedName("hostname") + @Param(description = "hostname") + private String hostname; + + @SerializedName("port") + @Param(description = "port") + private int port; + + public LdapConfigurationResponse() { + super(); + } + + public LdapConfigurationResponse(final String hostname) { + super(); + this.hostname = hostname; + } + + public LdapConfigurationResponse(final String hostname, final int port) { + this.hostname = hostname; + this.port = port; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public void setHostname(final String hostname) { + this.hostname = hostname; + } + + public void setPort(final int port) { + this.port = port; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapUserResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapUserResponse.java new file mode 100644 index 00000000000..9b21c8f54e8 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapUserResponse.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class LdapUserResponse extends BaseResponse { + @SerializedName("email") + @Param(description = "The user's email") + private String email; + + @SerializedName("principal") + @Param(description = "The user's principle") + private String principal; + + @SerializedName("firstname") + @Param(description = "The user's firstname") + private String firstname; + + @SerializedName("lastname") + @Param(description = "The user's lastname") + private String lastname; + + @SerializedName("username") + @Param(description = "The user's username") + private String username; + + public LdapUserResponse() { + super(); + } + + public LdapUserResponse(final String username, final String email, + final String firstname, final String lastname, + final String principal) { + super(); + this.username = username; + this.email = email; + this.firstname = firstname; + this.lastname = lastname; + this.principal = principal; + } + + public String getEmail() { + return email; + } + + public String getFirstname() { + return firstname; + } + + public String getLastname() { + return lastname; + } + + public String getPrincipal() { + return principal; + } + + public String getUsername() { + return username; + } + + public void setEmail(final String email) { + this.email = email; + } + + public void setFirstname(final String firstname) { + this.firstname = firstname; + } + + public void setLastname(final String lastname) { + this.lastname = lastname; + } + + public void setPrincipal(final String principal) { + this.principal = principal; + } + + public void setUsername(final String username) { + this.username = username; + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..559a9794b2d --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.log4j.Logger; + +import com.cloud.server.auth.DefaultUserAuthenticator; +import com.cloud.user.UserAccount; +import com.cloud.user.dao.UserAccountDao; + +public class LdapAuthenticator extends DefaultUserAuthenticator { + private static final Logger s_logger = Logger + .getLogger(LdapAuthenticator.class.getName()); + + @Inject + private LdapManager _ldapManager; + @Inject + private UserAccountDao _userAccountDao; + + public LdapAuthenticator() { + super(); + } + + public LdapAuthenticator(final LdapManager ldapManager, + final UserAccountDao userAccountDao) { + super(); + _ldapManager = ldapManager; + _userAccountDao = userAccountDao; + } + + @Override + public boolean authenticate(final String username, final String password, + final Long domainId, final Map 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; + } + } + + @Override + public String encode(final String password) { + return password; + } +} 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 new file mode 100644 index 00000000000..0cfb37c5d31 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.List; + +import javax.inject.Inject; +import javax.naming.directory.SearchControls; + +import org.apache.cloudstack.api.command.LdapListConfigurationCmd; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.utils.Pair; + +public class LdapConfiguration { + private final static String factory = "com.sun.jndi.ldap.LdapCtxFactory"; + + private final static int scope = SearchControls.SUBTREE_SCOPE; + + @Inject + private ConfigurationDao _configDao; + + @Inject + private LdapManager _ldapManager; + + public LdapConfiguration() { + } + + public LdapConfiguration(final ConfigurationDao configDao, + final LdapManager ldapManager) { + _configDao = configDao; + _ldapManager = ldapManager; + } + + public String getAuthentication() { + if ((getBindPrincipal() == null) && (getBindPassword() == null)) { + return "none"; + } else { + return "simple"; + } + } + + public String getBaseDn() { + return _configDao.getValue("ldap.basedn"); + } + + public String getBindPassword() { + return _configDao.getValue("ldap.bind.password"); + } + + public String getBindPrincipal() { + return _configDao.getValue("ldap.bind.principal"); + } + + public String getEmailAttribute() { + final String emailAttribute = _configDao + .getValue("ldap.email.attribute"); + return emailAttribute == null ? "mail" : emailAttribute; + } + + public String getFactory() { + return factory; + } + + public String getFirstnameAttribute() { + final String firstnameAttribute = _configDao + .getValue("ldap.firstname.attribute"); + return firstnameAttribute == null ? "givenname" : firstnameAttribute; + } + + public String getLastnameAttribute() { + final String lastnameAttribute = _configDao + .getValue("ldap.lastname.attribute"); + return lastnameAttribute == null ? "sn" : lastnameAttribute; + } + + public String getProviderUrl() { + final String protocol = getSSLStatus() == true ? "ldaps://" : "ldap://"; + final Pair, Integer> result = _ldapManager + .listConfigurations(new LdapListConfigurationCmd(_ldapManager)); + final StringBuilder providerUrls = new StringBuilder(); + String delim = ""; + for (final LdapConfigurationVO resource : result.first()) { + final String providerUrl = protocol + resource.getHostname() + ":" + + resource.getPort(); + providerUrls.append(delim).append(providerUrl); + delim = " "; + } + return providerUrls.toString(); + } + + public String[] getReturnAttributes() { + return new String[] { getUsernameAttribute(), getEmailAttribute(), + getFirstnameAttribute(), getLastnameAttribute() }; + } + + public int getScope() { + return scope; + } + + public String getSearchGroupPrinciple() { + return _configDao.getValue("ldap.search.group.principle"); + } + + public boolean getSSLStatus() { + boolean sslStatus = false; + if (getTrustStore() != null && getTrustStorePassword() != null) { + sslStatus = true; + } + return sslStatus; + } + + public String getTrustStore() { + return _configDao.getValue("ldap.truststore"); + } + + public String getTrustStorePassword() { + return _configDao.getValue("ldap.truststore.password"); + } + + public String getUsernameAttribute() { + final String usernameAttribute = _configDao + .getValue("ldap.username.attribute"); + return usernameAttribute == null ? "uid" : usernameAttribute; + } + + public String getUserObject() { + final String userObject = _configDao.getValue("ldap.user.object"); + return userObject == null ? "inetOrgPerson" : userObject; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfigurationVO.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfigurationVO.java new file mode 100644 index 00000000000..98e42f8af40 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfigurationVO.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "ldap_configuration") +public class LdapConfigurationVO implements InternalIdentity { + @Column(name = "hostname") + private String hostname; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "port") + private int port; + + public LdapConfigurationVO() { + } + + public LdapConfigurationVO(final String hostname, final int port) { + this.hostname = hostname; + this.port = port; + } + + public String getHostname() { + return hostname; + } + + @Override + public long getId() { + return id; + } + + public int getPort() { + return port; + } + + public void setId(final long id) { + this.id = id; + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapContextFactory.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapContextFactory.java new file mode 100644 index 00000000000..ceeed6862fb --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapContextFactory.java @@ -0,0 +1,136 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.Hashtable; + +import javax.inject.Inject; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.apache.log4j.Logger; + +public class LdapContextFactory { + private static final Logger s_logger = Logger + .getLogger(LdapContextFactory.class.getName()); + + @Inject + private LdapConfiguration _ldapConfiguration; + + public LdapContextFactory() { + } + + public LdapContextFactory(final LdapConfiguration ldapConfiguration) { + _ldapConfiguration = ldapConfiguration; + } + + public DirContext createBindContext() throws NamingException { + return createBindContext(null); + } + + public DirContext createBindContext(final String providerUrl) + throws NamingException { + final String bindPrincipal = _ldapConfiguration.getBindPrincipal(); + final String bindPassword = _ldapConfiguration.getBindPassword(); + return createInitialDirContext(bindPrincipal, bindPassword, + providerUrl, true); + } + + private DirContext createInitialDirContext(final String principal, + final String password, final boolean isSystemContext) + throws NamingException { + return createInitialDirContext(principal, password, null, + isSystemContext); + } + + private DirContext createInitialDirContext(final String principal, + final String password, final String providerUrl, + final boolean isSystemContext) throws NamingException { + return new InitialDirContext(getEnvironment(principal, password, + providerUrl, isSystemContext)); + } + + public DirContext createUserContext(final String principal, + final String password) throws NamingException { + return createInitialDirContext(principal, password, false); + } + + private void enableSSL(final Hashtable environment) { + final boolean sslStatus = _ldapConfiguration.getSSLStatus(); + + if (sslStatus) { + s_logger.info("LDAP SSL enabled."); + environment.put(Context.SECURITY_PROTOCOL, "ssl"); + System.setProperty("javax.net.ssl.trustStore", + _ldapConfiguration.getTrustStore()); + System.setProperty("javax.net.ssl.trustStorePassword", + _ldapConfiguration.getTrustStorePassword()); + } + } + + private Hashtable getEnvironment(final String principal, + final String password, final String providerUrl, + final boolean isSystemContext) { + final String factory = _ldapConfiguration.getFactory(); + final String url = providerUrl == null ? _ldapConfiguration + .getProviderUrl() : providerUrl; + + final Hashtable environment = new Hashtable(); + + environment.put(Context.INITIAL_CONTEXT_FACTORY, factory); + environment.put(Context.PROVIDER_URL, url); + environment.put("com.sun.jndi.ldap.read.timeout", "500"); + environment.put("com.sun.jndi.ldap.connect.pool", "true"); + + enableSSL(environment); + setAuthentication(environment, isSystemContext); + + if (principal != null) { + environment.put(Context.SECURITY_PRINCIPAL, principal); + } + + if (password != null) { + environment.put(Context.SECURITY_CREDENTIALS, password); + } + + return environment; + } + + private void setAuthentication(final Hashtable environment, + final boolean isSystemContext) { + final String authentication = _ldapConfiguration.getAuthentication(); + + if ("none".equals(authentication) && !isSystemContext) { + environment.put(Context.SECURITY_AUTHENTICATION, "simple"); + } else { + environment.put(Context.SECURITY_AUTHENTICATION, authentication); + } + } + + public void testConnection(final String providerUrl) throws NamingException { + try { + createBindContext(providerUrl); + s_logger.info("LDAP Connection was successful"); + } catch (final NamingException e) { + s_logger.warn("LDAP Connection failed"); + s_logger.error(e.getMessage(), e); + throw e; + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..683822d31f4 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.List; + +import javax.naming.NamingException; + +import org.apache.cloudstack.api.command.LdapListConfigurationCmd; +import org.apache.cloudstack.api.response.LdapConfigurationResponse; +import org.apache.cloudstack.api.response.LdapUserResponse; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.Pair; +import com.cloud.utils.component.PluggableService; + +public interface LdapManager extends PluggableService { + + LdapConfigurationResponse addConfiguration(String hostname, int port) + throws InvalidParameterValueException; + + boolean canAuthenticate(String username, String password); + + LdapConfigurationResponse createLdapConfigurationResponse( + LdapConfigurationVO configuration); + + LdapUserResponse createLdapUserResponse(LdapUser user); + + LdapConfigurationResponse deleteConfiguration(String hostname) + throws InvalidParameterValueException; + + LdapUser getUser(final String username) throws NamingException; + + List getUsers() throws NoLdapUserMatchingQueryException; + + boolean isLdapEnabled(); + + Pair, Integer> listConfigurations( + LdapListConfigurationCmd cmd); + + List searchUsers(String query) + throws NoLdapUserMatchingQueryException; +} \ 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 new file mode 100644 index 00000000000..87406ad9c34 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -0,0 +1,232 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.ArrayList; +import java.util.List; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.apache.cloudstack.api.LdapValidator; +import org.apache.cloudstack.api.command.LdapAddConfigurationCmd; +import org.apache.cloudstack.api.command.LdapCreateAccountCmd; +import org.apache.cloudstack.api.command.LdapDeleteConfigurationCmd; +import org.apache.cloudstack.api.command.LdapListConfigurationCmd; +import org.apache.cloudstack.api.command.LdapListUsersCmd; +import org.apache.cloudstack.api.command.LdapUserSearchCmd; +import org.apache.cloudstack.api.response.LdapConfigurationResponse; +import org.apache.cloudstack.api.response.LdapUserResponse; +import org.apache.cloudstack.ldap.dao.LdapConfigurationDao; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.Pair; + +@Component +@Local(value = LdapManager.class) +public class LdapManagerImpl implements LdapManager, LdapValidator { + private static final Logger s_logger = Logger + .getLogger(LdapManagerImpl.class.getName()); + + @Inject + private LdapConfigurationDao _ldapConfigurationDao; + + @Inject + private LdapContextFactory _ldapContextFactory; + + @Inject + private LdapUserManager _ldapUserManager; + + public LdapManagerImpl() { + super(); + } + + public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, + final LdapContextFactory ldapContextFactory, + final LdapUserManager ldapUserManager) { + super(); + _ldapConfigurationDao = ldapConfigurationDao; + _ldapContextFactory = ldapContextFactory; + _ldapUserManager = ldapUserManager; + } + + @Override + public LdapConfigurationResponse addConfiguration(final String hostname, + final int port) throws InvalidParameterValueException { + LdapConfigurationVO configuration = _ldapConfigurationDao + .findByHostname(hostname); + if (configuration == null) { + try { + final String providerUrl = "ldap://" + hostname + ":" + port; + _ldapContextFactory.createBindContext(providerUrl); + configuration = new LdapConfigurationVO(hostname, port); + _ldapConfigurationDao.persist(configuration); + s_logger.info("Added new ldap server with hostname: " + + hostname); + return new LdapConfigurationResponse(hostname, port); + } catch (final NamingException e) { + throw new InvalidParameterValueException( + "Unable to bind to the given LDAP server"); + } + } else { + throw new InvalidParameterValueException("Duplicate configuration"); + } + } + + @Override + public boolean canAuthenticate(final String username, final String password) { + final String escapedUsername = LdapUtils + .escapeLDAPSearchFilter(username); + try { + final LdapUser user = getUser(escapedUsername); + final String principal = user.getPrincipal(); + final DirContext context = _ldapContextFactory.createUserContext( + principal, password); + closeContext(context); + return true; + } catch (final NamingException e) { + s_logger.info("Failed to authenticate user: " + username + + ". incorrect password."); + return false; + } + } + + private void closeContext(final DirContext context) { + try { + if (context != null) { + context.close(); + } + } catch (final NamingException e) { + s_logger.warn(e.getMessage()); + } + } + + @Override + public LdapConfigurationResponse createLdapConfigurationResponse( + final LdapConfigurationVO configuration) { + final LdapConfigurationResponse response = new LdapConfigurationResponse(); + response.setHostname(configuration.getHostname()); + response.setPort(configuration.getPort()); + return response; + } + + @Override + public LdapUserResponse createLdapUserResponse(final LdapUser user) { + final LdapUserResponse response = new LdapUserResponse(); + response.setUsername(user.getUsername()); + response.setFirstname(user.getFirstname()); + response.setLastname(user.getLastname()); + response.setEmail(user.getEmail()); + response.setPrincipal(user.getPrincipal()); + return response; + } + + @Override + public LdapConfigurationResponse deleteConfiguration(final String hostname) + throws InvalidParameterValueException { + final LdapConfigurationVO configuration = _ldapConfigurationDao + .findByHostname(hostname); + if (configuration == null) { + throw new InvalidParameterValueException( + "Cannot find configuration with hostname " + hostname); + } else { + _ldapConfigurationDao.remove(configuration.getId()); + s_logger.info("Removed ldap server with hostname: " + hostname); + return new LdapConfigurationResponse(configuration.getHostname(), + configuration.getPort()); + } + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + cmdList.add(LdapUserSearchCmd.class); + cmdList.add(LdapListUsersCmd.class); + cmdList.add(LdapAddConfigurationCmd.class); + cmdList.add(LdapDeleteConfigurationCmd.class); + cmdList.add(LdapListConfigurationCmd.class); + cmdList.add(LdapCreateAccountCmd.class); + return cmdList; + } + + @Override + public LdapUser getUser(final String username) throws NamingException { + DirContext context = null; + try { + context = _ldapContextFactory.createBindContext(); + + final String escapedUsername = LdapUtils + .escapeLDAPSearchFilter(username); + return _ldapUserManager.getUser(escapedUsername, context); + + } catch (final NamingException e) { + throw e; + } finally { + closeContext(context); + } + } + + @Override + public List getUsers() throws NoLdapUserMatchingQueryException { + DirContext context = null; + try { + context = _ldapContextFactory.createBindContext(); + return _ldapUserManager.getUsers(context); + } catch (final NamingException e) { + throw new NoLdapUserMatchingQueryException("*"); + } finally { + closeContext(context); + } + } + + @Override + public boolean isLdapEnabled() { + return listConfigurations(new LdapListConfigurationCmd(this)).second() > 0; + } + + @Override + public Pair, Integer> listConfigurations( + final LdapListConfigurationCmd cmd) { + final String hostname = cmd.getHostname(); + final int port = cmd.getPort(); + final Pair, Integer> result = _ldapConfigurationDao + .searchConfigurations(hostname, port); + return new Pair, Integer>( + result.first(), result.second()); + } + + @Override + public List searchUsers(final String username) + throws NoLdapUserMatchingQueryException { + DirContext context = null; + try { + context = _ldapContextFactory.createBindContext(); + final String escapedUsername = LdapUtils + .escapeLDAPSearchFilter(username); + return _ldapUserManager.getUsers("*" + escapedUsername + "*", + context); + } catch (final NamingException e) { + throw new NoLdapUserMatchingQueryException(username); + } finally { + closeContext(context); + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..18ad7d95119 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +public class LdapUser implements Comparable { + private final String email; + private final String principal; + private final String firstname; + private final String lastname; + private final String username; + + public LdapUser(final String username, final String email, + final String firstname, final String lastname, + final String principal) { + this.username = username; + this.email = email; + this.firstname = firstname; + this.lastname = lastname; + this.principal = principal; + } + + @Override + public int compareTo(final LdapUser other) { + return getUsername().compareTo(other.getUsername()); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other instanceof LdapUser) { + final LdapUser otherLdapUser = (LdapUser) other; + return getUsername().equals(otherLdapUser.getUsername()); + } + return false; + } + + public String getEmail() { + return email; + } + + public String getFirstname() { + return firstname; + } + + public String getLastname() { + return lastname; + } + + public String getPrincipal() { + return principal; + } + + public String getUsername() { + return username; + } + + @Override + public int hashCode() { + return getUsername().hashCode(); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..7494346856a --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java @@ -0,0 +1,140 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import java.util.ArrayList; +import java.util.Collections; +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; + +public class LdapUserManager { + + @Inject + private LdapConfiguration _ldapConfiguration; + + public LdapUserManager() { + } + + public LdapUserManager(final LdapConfiguration ldapConfiguration) { + _ldapConfiguration = ldapConfiguration; + } + + private LdapUser createUser(final SearchResult result) + throws NamingException { + final Attributes attributes = result.getAttributes(); + + final String username = LdapUtils.getAttributeValue(attributes, + _ldapConfiguration.getUsernameAttribute()); + final String email = LdapUtils.getAttributeValue(attributes, + _ldapConfiguration.getEmailAttribute()); + final String firstname = LdapUtils.getAttributeValue(attributes, + _ldapConfiguration.getFirstnameAttribute()); + final String lastname = LdapUtils.getAttributeValue(attributes, + _ldapConfiguration.getLastnameAttribute()); + final String principal = result.getName() + "," + + _ldapConfiguration.getBaseDn(); + + return new LdapUser(username, email, firstname, lastname, principal); + } + + private String generateSearchFilter(final String username) { + 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 (_ldapConfiguration.getSearchGroupPrinciple() != null) { + memberOfFilter.append("(memberof="); + memberOfFilter.append(_ldapConfiguration.getSearchGroupPrinciple()); + memberOfFilter.append(")"); + } + + final StringBuilder result = new StringBuilder(); + result.append("(&"); + result.append(userObjectFilter); + result.append(usernameFilter); + result.append(memberOfFilter); + 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()) { + return createUser(result.nextElement()); + } else { + throw new NamingException("No user found for username " + username); + } + } + + public List getUsers(final DirContext context) + throws NamingException { + return getUsers(null, context); + } + + public List getUsers(final String username, + final DirContext context) throws NamingException { + final NamingEnumeration results = searchUsers(username, + context); + + final List users = new ArrayList(); + + while (results.hasMoreElements()) { + final SearchResult result = results.nextElement(); + users.add(createUser(result)); + } + + Collections.sort(users); + + return users; + } + + public NamingEnumeration searchUsers(final DirContext context) + throws NamingException { + return searchUsers(null, context); + } + + public NamingEnumeration searchUsers(final String username, + final DirContext context) throws NamingException { + final SearchControls controls = new SearchControls(); + + controls.setSearchScope(_ldapConfiguration.getScope()); + controls.setReturningAttributes(_ldapConfiguration + .getReturnAttributes()); + + return context.search(_ldapConfiguration.getBaseDn(), + generateSearchFilter(username), controls); + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUtils.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUtils.java new file mode 100644 index 00000000000..e1f999b72df --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUtils.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +public final class LdapUtils { + public static String escapeLDAPSearchFilter(final String filter) { + final StringBuilder sb = new StringBuilder(); + for (final char character : filter.toCharArray()) { + switch (character) { + case '\\': + sb.append("\\5c"); + break; + case '*': + sb.append("\\2a"); + break; + case '(': + sb.append("\\28"); + break; + case ')': + sb.append("\\29"); + break; + case '\u0000': + sb.append("\\00"); + break; + default: + sb.append(character); + } + } + return sb.toString(); + } + + public static String getAttributeValue(final Attributes attributes, + final String attributeName) throws NamingException { + final Attribute attribute = attributes.get(attributeName); + if (attribute != null) { + final Object value = attribute.get(); + return String.valueOf(value); + } + return null; + } + + private LdapUtils() { + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryException.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryException.java new file mode 100644 index 00000000000..d7a3744221d --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryException.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap; + +public class NoLdapUserMatchingQueryException extends Exception { + private static final long serialVersionUID = 7124360347208388174L; + + private final String query; + + public NoLdapUserMatchingQueryException(final String query) { + super("No users matching: " + query); + this.query = query; + } + + public String getQuery() { + return query; + } +} \ No newline at end of file diff --git a/api/src/org/apache/cloudstack/api/response/LDAPRemoveResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoSuchLdapUserException.java similarity index 68% rename from api/src/org/apache/cloudstack/api/response/LDAPRemoveResponse.java rename to plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoSuchLdapUserException.java index 0feec5bc5b5..91279ae893c 100644 --- a/api/src/org/apache/cloudstack/api/response/LDAPRemoveResponse.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/NoSuchLdapUserException.java @@ -14,13 +14,18 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package org.apache.cloudstack.api.response; +package org.apache.cloudstack.ldap; -import org.apache.cloudstack.api.BaseResponse; +public class NoSuchLdapUserException extends Exception { + private static final long serialVersionUID = 6782938919658010900L; + private final String username; -public class LDAPRemoveResponse extends BaseResponse { + public NoSuchLdapUserException(final String username) { + super("No such user: " + username); + this.username = username; + } - public LDAPRemoveResponse(){ - super(); - } + public String getUsername() { + return username; + } } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java new file mode 100644 index 00000000000..f74bb9cd8f9 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap.dao; + +import java.util.List; + +import org.apache.cloudstack.ldap.LdapConfigurationVO; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +public interface LdapConfigurationDao extends + GenericDao { + LdapConfigurationVO findByHostname(String hostname); + + Pair, Integer> searchConfigurations( + String hostname, int port); +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java new file mode 100644 index 00000000000..862206ddf23 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.ldap.dao; + +import java.util.List; + +import javax.ejb.Local; + +import org.apache.cloudstack.ldap.LdapConfigurationVO; +import org.springframework.stereotype.Component; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +@Component +@Local(value = { LdapConfigurationDao.class }) +public class LdapConfigurationDaoImpl extends + GenericDaoBase implements + LdapConfigurationDao { + private final SearchBuilder hostnameSearch; + private final SearchBuilder listAllConfigurationsSearch; + + public LdapConfigurationDaoImpl() { + super(); + hostnameSearch = createSearchBuilder(); + hostnameSearch.and("hostname", hostnameSearch.entity().getHostname(), + SearchCriteria.Op.EQ); + hostnameSearch.done(); + + listAllConfigurationsSearch = createSearchBuilder(); + listAllConfigurationsSearch.and("hostname", listAllConfigurationsSearch + .entity().getHostname(), Op.EQ); + listAllConfigurationsSearch.and("port", listAllConfigurationsSearch + .entity().getPort(), Op.EQ); + listAllConfigurationsSearch.done(); + } + + @Override + public LdapConfigurationVO findByHostname(final String hostname) { + final SearchCriteria sc = hostnameSearch.create(); + sc.setParameters("hostname", hostname); + return findOneBy(sc); + } + + @Override + public Pair, Integer> searchConfigurations( + final String hostname, final int port) { + final SearchCriteria sc = listAllConfigurationsSearch + .create(); + if (hostname != null) { + sc.setParameters("hostname", hostname); + } + return searchAndCount(sc, null); + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/BasicNamingEnumerationImpl.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/BasicNamingEnumerationImpl.groovy new file mode 100644 index 00000000000..32101601571 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/BasicNamingEnumerationImpl.groovy @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import javax.naming.NamingEnumeration +import javax.naming.NamingException +import javax.naming.directory.SearchResult + +class BasicNamingEnumerationImpl implements NamingEnumeration { + + private LinkedList items = new LinkedList(); + + public void add(SearchResult item) { + items.add(item) + } + + @Override + public void close() throws NamingException { + } + + @Override + public boolean hasMore() throws NamingException { + return hasMoreElements(); + } + + @Override + public boolean hasMoreElements() { + return items.size != 0; + } + + @Override + public Object next() throws NamingException { + return nextElement(); + } + + @Override + public Object nextElement() { + SearchResult result = items.getFirst(); + items.removeFirst(); + return result; + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAddConfigurationCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAddConfigurationCmdSpec.groovy new file mode 100644 index 00000000000..b7e2f45f3ec --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAddConfigurationCmdSpec.groovy @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import com.cloud.exception.InvalidParameterValueException +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LdapAddConfigurationCmd +import org.apache.cloudstack.api.response.LdapConfigurationResponse +import org.apache.cloudstack.ldap.LdapManager + +class LdapAddConfigurationCmdSpec extends spock.lang.Specification { + + def "Test failed response from execute"() { + given: "We have an LDAP manager, no configuration and a LdapAddConfigurationCmd" + def ldapManager = Mock(LdapManager) + ldapManager.addConfiguration(_, _) >> { throw new InvalidParameterValueException() } + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "LdapAddCofnigurationCmd is executed" + ldapAddConfigurationCmd.execute() + then: "an exception is thrown" + thrown ServerApiException + } + + def "Test getEntityOwnerId is 1"() { + given: "We have an LdapManager and LdapConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "Get Entity Owner Id is called" + long ownerId = ldapAddConfigurationCmd.getEntityOwnerId() + then: "1 is returned" + ownerId == 1 + } + + def "Test successful response from execute"() { + given: "We have an LDAP Manager that has a configuration and a LdapAddConfigurationCmd" + def ldapManager = Mock(LdapManager) + ldapManager.addConfiguration(_, _) >> new LdapConfigurationResponse("localhost", 389) + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "LdapAddConfigurationCmd is executed" + ldapAddConfigurationCmd.execute() + then: "the responseObject should have the hostname localhost and port 389" + ldapAddConfigurationCmd.responseObject.hostname == "localhost" + ldapAddConfigurationCmd.responseObject.port == 389 + } + + def "Test successful return of getCommandName"() { + given: "We have an LdapManager and LdapConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "Get Command Name is called" + String commandName = ldapAddConfigurationCmd.getCommandName() + then: "ldapconfigurationresponse is returned" + commandName == "ldapconfigurationresponse" + } + + def "Test successful setting of hostname"() { + given: "We have an LdapManager and LdapAddConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "The hostname is set" + ldapAddConfigurationCmd.setHostname("localhost") + then: "Get hostname returns the set hostname" + ldapAddConfigurationCmd.getHostname() == "localhost" + } + + def "Test successful setting of port"() { + given: "We have an LdapManager and LdapAddConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapAddConfigurationCmd = new LdapAddConfigurationCmd(ldapManager) + when: "The port is set" + ldapAddConfigurationCmd.setPort(389) + then: "Get port returns the port" + ldapAddConfigurationCmd.getPort() == 389 + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy new file mode 100644 index 00000000000..416c1330359 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import com.cloud.user.UserAccountVO +import com.cloud.user.dao.UserAccountDao +import com.cloud.utils.Pair +import org.apache.cloudstack.ldap.LdapAuthenticator +import org.apache.cloudstack.ldap.LdapConfigurationVO +import org.apache.cloudstack.ldap.LdapManager + +class LdapAuthenticatorSpec extends spock.lang.Specification { + + def "Test a failed authentication due to user not being found within cloudstack"() { + given: "We have an LdapManager, userAccountDao and ldapAuthenticator and the user doesn't exist within cloudstack." + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + userAccountDao.getUserAccount(_, _) >> null + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + when: "A user authentications" + def result = ldapAuthenticator.authenticate("rmurphy", "password", 0, null) + then: "their authentication fails" + result == false + } + + def "Test failed authentication due to ldap bind being unsuccessful"() { + given: "We have an LdapManager, LdapConfiguration, userAccountDao and LdapAuthenticator" + def ldapManager = Mock(LdapManager) + ldapManager.isLdapEnabled() >> true + ldapManager.canAuthenticate(_, _) >> false + + UserAccountDao userAccountDao = Mock(UserAccountDao) + userAccountDao.getUserAccount(_, _) >> new UserAccountVO() + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + + when: "The user authenticates with an incorrect password" + def result = ldapAuthenticator.authenticate("rmurphy", "password", 0, null) + + then: "their authentication fails" + result == false + } + + def "Test failed authentication due to ldap not being configured"() { + given: "We have an LdapManager, A configured LDAP server, a userAccountDao and LdapAuthenticator" + def ldapManager = Mock(LdapManager) + ldapManager.isLdapEnabled() >> false + + UserAccountDao userAccountDao = Mock(UserAccountDao) + userAccountDao.getUserAccount(_, _) >> new UserAccountVO() + + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + when: "The user authenticates" + def result = ldapAuthenticator.authenticate("rmurphy", "password", 0, null) + then: "their authentication fails" + result == false + } + + def "Test successful authentication"() { + given: "We have an LdapManager, LdapConfiguration, userAccountDao and LdapAuthenticator" + def ldapManager = Mock(LdapManager) + ldapManager.isLdapEnabled() >> true + ldapManager.canAuthenticate(_, _) >> true + + UserAccountDao userAccountDao = Mock(UserAccountDao) + userAccountDao.getUserAccount(_, _) >> new UserAccountVO() + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + + when: "The user authenticates with an incorrect password" + def result = ldapAuthenticator.authenticate("rmurphy", "password", 0, null) + + then: "their authentication passes" + result == true + } + + def "Test that encode doesn't change the input"() { + given: "We have an LdapManager, userAccountDao and LdapAuthenticator" + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + when: "a users password is encoded" + def result = ldapAuthenticator.encode("password") + then: "it doesn't change" + result == "password" + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationDaoImplSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationDaoImplSpec.groovy new file mode 100644 index 00000000000..144890957f2 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationDaoImplSpec.groovy @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl + +class LdapConfigurationDaoImplSpec extends spock.lang.Specification { + def "Test setting up of a LdapConfigurationDao"() { + given: "We have an LdapConfigurationDao implementation" + def ldapConfigurationDaoImpl = new LdapConfigurationDaoImpl(); + expect: "that hostnameSearch and listAllConfigurationsSearch is configured" + ldapConfigurationDaoImpl.hostnameSearch != null; + ldapConfigurationDaoImpl.listAllConfigurationsSearch != null + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationResponseSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationResponseSpec.groovy new file mode 100644 index 00000000000..6f7a3701e25 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationResponseSpec.groovy @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.api.response.LdapConfigurationResponse + +class LdapConfigurationResponseSpec extends spock.lang.Specification { + def "Testing succcessful setting of LdapConfigurationResponse hostname"() { + given: "We have a LdapConfigurationResponse" + LdapConfigurationResponse response = new LdapConfigurationResponse(); + when: "The hostname is set" + response.setHostname("localhost"); + then: "Get hostname should return the set value" + response.getHostname() == "localhost"; + } + + def "Testing successful setting of LdapConfigurationResponse hostname and port via constructor"() { + given: "We have a LdapConfiguration response" + LdapConfigurationResponse response + when: "both hostname and port are set by constructor" + response = new LdapConfigurationResponse("localhost", 389) + then: "Get hostname and port should return the set values." + response.getHostname() == "localhost" + response.getPort() == 389 + } + + def "Testing successful setting of LdapConfigurationResponse port"() { + given: "We have a LdapConfigurationResponse" + LdapConfigurationResponse response = new LdapConfigurationResponse() + when: "The port is set" + response.setPort(389) + then: "Get port should return the set value" + response.getPort() == 389 + } +} 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 new file mode 100644 index 00000000000..c5939593059 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.utils.Pair +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.ldap.LdapConfiguration +import org.apache.cloudstack.ldap.LdapConfigurationVO +import org.apache.cloudstack.ldap.LdapManager + +import javax.naming.directory.SearchControls + +class LdapConfigurationSpec extends spock.lang.Specification { + def "Test that getAuthentication returns none"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get authentication is called" + String authentication = ldapConfiguration.getAuthentication() + then: "none should be returned" + authentication == "none" + } + + def "Test that getAuthentication returns simple"() { + given: "We have a configDao, LdapManager and LdapConfiguration with bind principle and password set" + def configDao = Mock(ConfigurationDao) + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + configDao.getValue("ldap.bind.password") >> "password" + configDao.getValue("ldap.bind.principal") >> "cn=rmurphy,dc=cloudstack,dc=org" + when: "Get authentication is called" + String authentication = ldapConfiguration.getAuthentication() + then: "authentication should be set to simple" + authentication == "simple" + } + + def "Test that getBaseDn returns dc=cloudstack,dc=org"() { + given: "We have a ConfigDao, LdapManager and ldapConfiguration with a baseDn value set." + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.basedn") >> "dc=cloudstack,dc=org" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get basedn is called" + String baseDn = ldapConfiguration.getBaseDn(); + then: "The set baseDn should be returned" + baseDn == "dc=cloudstack,dc=org" + } + + def "Test that getEmailAttribute returns mail"() { + given: "Given that we have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.email.attribute") >> "mail" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get Email Attribute is called" + String emailAttribute = ldapConfiguration.getEmailAttribute() + then: "mail should be returned" + emailAttribute == "mail" + } + + def "Test that getFactory returns com.sun.jndi.ldap.LdapCtxFactory"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get Factory is scalled" + String factory = ldapConfiguration.getFactory(); + then: "com.sun.jndi.ldap.LdapCtxFactory is returned" + factory == "com.sun.jndi.ldap.LdapCtxFactory" + } + + def "Test that getFirstnameAttribute returns givenname"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.firstname.attribute") >> "givenname" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get firstname attribute is called" + String firstname = ldapConfiguration.getFirstnameAttribute() + then: "givennam should be returned" + firstname == "givenname" + } + + def "Test that getLastnameAttribute returns givenname"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.lastname.attribute") >> "sn" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get Lastname Attribute is scalled " + String lastname = ldapConfiguration.getLastnameAttribute() + then: "sn should be returned" + lastname == "sn" + } + + def "Test that getReturnAttributes returns the correct data"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.firstname.attribute") >> "givenname" + configDao.getValue("ldap.lastname.attribute") >> "sn" + configDao.getValue("ldap.username.attribute") >> "uid" + configDao.getValue("ldap.email.attribute") >> "mail" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get return attributes is called" + String[] returnAttributes = ldapConfiguration.getReturnAttributes() + then: "An array containing uid, mail, givenname and sn is returned" + returnAttributes == ["uid", "mail", "givenname", "sn"] + } + + def "Test that getScope returns SearchControls.SUBTREE_SCOPE"() { + given: "We have ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get scope is called" + int scope = ldapConfiguration.getScope() + then: "SearchControls.SUBTRE_SCOPE should be returned" + scope == SearchControls.SUBTREE_SCOPE; + } + + def "Test that getUsernameAttribute returns uid"() { + given: "We have ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.username.attribute") >> "uid" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get Username Attribute is called" + String usernameAttribute = ldapConfiguration.getUsernameAttribute() + then: "uid should be returned" + usernameAttribute == "uid" + } + + def "Test that getUserObject returns inetOrgPerson"() { + given: "We have a ConfigDao, LdapManager and LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.user.object") >> "inetOrgPerson" + def ldapManager = Mock(LdapManager) + def ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + when: "Get user object is called" + String userObject = ldapConfiguration.getUserObject() + then: "inetOrgPerson is returned" + userObject == "inetOrgPerson" + } + + def "Test that providerUrl successfully returns a URL when a configuration is available"() { + given: "We have a ConfigDao, LdapManager, LdapConfiguration" + def configDao = Mock(ConfigurationDao) + def ldapManager = Mock(LdapManager) + List ldapConfigurationList = new ArrayList() + ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389)) + Pair, Integer> result = new Pair, Integer>(); + result.set(ldapConfigurationList, ldapConfigurationList.size()) + ldapManager.listConfigurations(_) >> result + + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + + when: "A request is made to get the providerUrl" + String providerUrl = ldapConfiguration.getProviderUrl() + + then: "The providerUrl should be given." + providerUrl == "ldap://localhost:389" + } + + def "Test that get search group principle returns successfully"() { + given: "We have a ConfigDao with a value for ldap.search.group.principle and an LdapConfiguration" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.search.group.principle") >> "cn=cloudstack,cn=users,dc=cloudstack,dc=org" + def ldapManager = Mock(LdapManager) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + + when: "A request is made to get the search group principle" + String result = ldapConfiguration.getSearchGroupPrinciple(); + + then: "The result holds the same value configDao did" + result == "cn=cloudstack,cn=users,dc=cloudstack,dc=org" + } + + def "Test that getTrustStorePassword resopnds"() { + given: "We have a ConfigDao with a value for truststore password" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.truststore.password") >> "password" + def ldapManager = Mock(LdapManager) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + + when: "A request is made to get the truststore password" + String result = ldapConfiguration.getTrustStorePassword() + + then: "The result is password" + result == "password"; + } + + def "Test that getSSLStatus can be true"() { + given: "We have a ConfigDao with values for truststore and truststore password set" + def configDao = Mock(ConfigurationDao) + configDao.getValue("ldap.truststore") >> "/tmp/ldap.ts" + configDao.getValue("ldap.truststore.password") >> "password" + def ldapManager = Mock(LdapManager) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManager) + + when: "A request is made to get the status of SSL" + boolean result = ldapConfiguration.getSSLStatus(); + + then: "The response should be true" + result == true + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationVOSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationVOSpec.groovy new file mode 100644 index 00000000000..3e713fe2cd3 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationVOSpec.groovy @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.LdapConfigurationVO + + +class LdapConfigurationVOSpec extends spock.lang.Specification { + def "Testing that the ID hostname and port is correctly set within the LDAP configuration VO"() { + given: "You have created an LDAP Configuration VO" + def configuration = new LdapConfigurationVO(hostname, port) + configuration.setId(id) + expect: "The id hostname and port is equal to the given data source" + configuration.getId() == id + configuration.getHostname() == hostname + configuration.getPort() == port + where: "The id, hostname and port is set to " + hostname << ["", null, "localhost"] + id << [0, 1000, -1000] + port << [0, 1000, -1000] + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapContextFactorySpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapContextFactorySpec.groovy new file mode 100644 index 00000000000..0b8f2848a13 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapContextFactorySpec.groovy @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.LdapConfiguration +import org.apache.cloudstack.ldap.LdapContextFactory +import spock.lang.Shared + +import javax.naming.NamingException +import javax.naming.directory.SearchControls +import javax.naming.ldap.LdapContext + +class LdapContextFactorySpec extends spock.lang.Specification { + @Shared + private def ldapConfiguration + + @Shared + private def username + + @Shared + private def principal + + @Shared + private def password + + def setupSpec() { + ldapConfiguration = Mock(LdapConfiguration) + + ldapConfiguration.getFactory() >> "com.sun.jndi.ldap.LdapCtxFactory" + ldapConfiguration.getProviderUrl() >> "ldap://localhost:389" + ldapConfiguration.getAuthentication() >> "none" + ldapConfiguration.getScope() >> SearchControls.SUBTREE_SCOPE + ldapConfiguration.getReturnAttributes() >> ["uid", "mail", "cn"] + ldapConfiguration.getUsernameAttribute() >> "uid" + ldapConfiguration.getEmailAttribute() >> "mail" + ldapConfiguration.getFirstnameAttribute() >> "givenname" + ldapConfiguration.getLastnameAttribute() >> "sn" + ldapConfiguration.getBaseDn() >> "dc=cloudstack,dc=org" + ldapConfiguration.getSSLStatus() >> true + ldapConfiguration.getTrustStore() >> "/tmp/ldap.ts" + ldapConfiguration.getTrustStorePassword() >> "password" + + username = "rmurphy" + principal = "cn=" + username + "," + ldapConfiguration.getBaseDn() + password = "password" + } + + def "Test succcessfully creating a initial context"() { + given: "We have a LdapContextFactory" + def ldapContextFactory = new LdapContextFactory(ldapConfiguration) + when: "A context attempts to bind and no Ldap server is avaiable" + ldapContextFactory.createInitialDirContext(null, null, true) + then: "An expection is thrown" + thrown NamingException + } + + def "Test successful failed connection"() { + given: "We have a LdapContextFactory" + def ldapContextFactory = Spy(LdapContextFactory, constructorArgs: [ldapConfiguration]) + when: "Test connection is executed" + ldapContextFactory.testConnection(ldapConfiguration.getProviderUrl()) + then: "An exception is thrown" + thrown NamingException + } + + def "Test successfully binding as a user"() { + given: "We have a LdapContextFactory" + def ldapContextFactory = new LdapContextFactory(ldapConfiguration) + when: "A user attempts to bind and no LDAP server is avaiable" + ldapContextFactory.createUserContext(principal, password) + then: "An exception is thrown" + thrown NamingException + } + + def "Test successfully creating a environment with username and password"() { + given: "We have an LdapContextFactory" + def ldapContextFactory = new LdapContextFactory(ldapConfiguration) + + when: "A request for an environment is made" + def result = ldapContextFactory.getEnvironment(null, null, null, true) + + then: "The resulting values should be set" + result['java.naming.provider.url'] == ldapConfiguration.getProviderUrl() + result['java.naming.factory.initial'] == ldapConfiguration.getFactory() + result['java.naming.security.principal'] == null + result['java.naming.security.authentication'] == ldapConfiguration.getAuthentication() + result['java.naming.security.credentials'] == null + } + + def "Test successfully creating a system environment with anon bind"() { + given: "We have an LdapContext Factory" + def ldapContextFactory = new LdapContextFactory(ldapConfiguration) + + when: "A request for an environment is made" + def result = ldapContextFactory.getEnvironment(principal, password, null, false) + + then: "The resulting values should be set" + result['java.naming.provider.url'] == ldapConfiguration.getProviderUrl() + result['java.naming.factory.initial'] == ldapConfiguration.getFactory() + result['java.naming.security.principal'] == principal + result['java.naming.security.authentication'] == "simple" + result['java.naming.security.credentials'] == password + } + + def "Test successully binding as system"() { + given: "We have a LdapContextFactory" + def ldapContextFactory = new LdapContextFactory(ldapConfiguration) + when: "A bind context attempts to bind and no Ldap server is avaiable" + ldapContextFactory.createBindContext() + then: "An exception is thrown" + thrown NamingException + } +} 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 new file mode 100644 index 00000000000..cc849defef5 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy @@ -0,0 +1,155 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import com.cloud.exception.InvalidParameterValueException +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LdapAddConfigurationCmd +import org.apache.cloudstack.api.response.LdapConfigurationResponse + +import org.apache.cloudstack.ldap.LdapUser; +import org.apache.cloudstack.ldap.LdapManager; + +import org.apache.cloudstack.api.command.LdapCreateAccountCmd; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.user.AccountService; +import com.cloud.user.UserAccount; +import com.cloud.user.UserAccountVO; + +import javax.naming.NamingException + +class LdapCreateAccountCmdSpec extends spock.lang.Specification { + + def "Test failure to retrive LDAP user"() { + given: "We have an LdapManager, AccountService and LdapCreateAccountCmd and LDAP user that doesn't exist" + LdapManager ldapManager = Mock(LdapManager) + ldapManager.getUser(_) >> { throw new NamingException() } + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = Spy(LdapCreateAccountCmd, constructorArgs: [ldapManager, accountService]) + ldapCreateAccountCmd.getCurrentContext() >> Mock(CallContext) + CallContext context = ldapCreateAccountCmd.getCurrentContext() + when: "An an account is created" + ldapCreateAccountCmd.execute() + then: "It fails and an exception is thrown" + thrown ServerApiException + } + + 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") + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = Spy(LdapCreateAccountCmd, constructorArgs: [ldapManager, accountService]) + ldapCreateAccountCmd.getCurrentContext() >> Mock(CallContext) + ldapCreateAccountCmd.createCloudstackUserAccount(_) >> null + when: "Cloudstack fail to create the user" + ldapCreateAccountCmd.execute() + then: "An exception is thrown" + thrown ServerApiException + } + + def "Test command name"() { + given: "We have an LdapManager, AccountService and LdapCreateAccountCmd" + LdapManager ldapManager = Mock(LdapManager) + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService) + when: "Get command name is called" + def result = ldapCreateAccountCmd.getCommandName() + then: "createaccountresponse is returned" + result == "createaccountresponse" + } + + def "Test getEntityOwnerId is 1"() { + given: "We have an LdapManager, AccountService andL dapCreateAccount" + LdapManager ldapManager = Mock(LdapManager) + AccountService accountService = Mock(AccountService) + + def ldapCreateAccountCmd = Spy(LdapCreateAccountCmd, constructorArgs: [ldapManager, accountService]) + when: "Get entity owner id is called" + long ownerId = ldapCreateAccountCmd.getEntityOwnerId() + then: "1 is returned" + ownerId == 1 + } + + def "Test password generation"() { + given: "We have an LdapManager, AccountService and LdapCreateAccountCmd" + LdapManager ldapManager = Mock(LdapManager) + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService) + when: "A password is generated" + def result = ldapCreateAccountCmd.generatePassword() + then: "The result shouldn't be null or empty" + result != "" + result != null + } + + def "Test validate User"() { + given: "We have an LdapManager, AccountService andL dapCreateAccount" + LdapManager ldapManager = Mock(LdapManager) + 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")) + then: "the result is true" + result == true + } + + def "Test validate User empty email"() { + given: "We have an LdapManager, AccountService andL dapCreateAccount" + LdapManager ldapManager = Mock(LdapManager) + 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")) + then: "An exception is thrown" + thrown Exception + } + + def "Test validate User empty firstname"() { + given: "We have an LdapManager, AccountService andL dapCreateAccount" + LdapManager ldapManager = Mock(LdapManager) + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = new LdapCreateAccountCmd(ldapManager, accountService) + when: "A user with no firstname attempts to validate" + ldapCreateAccountCmd.validateUser(new LdapUser("username","email",null,"lastname","principal")) + then: "An exception is thrown" + thrown Exception + } + + def "Test validate User empty lastname"() { + given: "We have an LdapManager, AccountService and LdapCreateAccountCmd" + LdapManager ldapManager = Mock(LdapManager) + 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")) + then: "An exception is thown" + thrown Exception + } + + def "Test validation of a user"() { + given: "We have an LdapManager, AccountService andL dapCreateAccount" + LdapManager ldapManager = Mock(LdapManager) + AccountService accountService = Mock(AccountService) + def ldapCreateAccountCmd = Spy(LdapCreateAccountCmd, constructorArgs: [ldapManager, accountService]) + when: "Get command name is called" + def commandName = ldapCreateAccountCmd.getCommandName() + then: "createaccountresponse is returned" + commandName == "createaccountresponse" + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapDeleteConfigurationCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapDeleteConfigurationCmdSpec.groovy new file mode 100644 index 00000000000..31d56ef68cb --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapDeleteConfigurationCmdSpec.groovy @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import com.cloud.exception.InvalidParameterValueException +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LdapDeleteConfigurationCmd +import org.apache.cloudstack.api.response.LdapConfigurationResponse +import org.apache.cloudstack.ldap.LdapManager + +class LdapDeleteConfigurationCmdSpec extends spock.lang.Specification { + + def "Test failed response from execute"() { + given: "We have an LdapManager and LdapDeleteConfigurationCmd" + def ldapManager = Mock(LdapManager) + ldapManager.deleteConfiguration(_) >> { throw new InvalidParameterValueException() } + def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) + when:"LdapDeleteConfigurationCmd is executed and no configuration exists" + ldapDeleteConfigurationCmd.execute() + then: "An exception is thrown" + thrown ServerApiException + } + + def "Test getEntityOwnerId is 1"() { + given: "We have an LdapManager and LdapDeleteConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) + when: "Get entity owner id is called" + long ownerId = ldapDeleteConfigurationCmd.getEntityOwnerId() + then: "1 is returned" + ownerId == 1 + } + + def "Test successful response from execute"() { + given: "We have an LdapManager and LdapDeleteConfigurationCmd" + def ldapManager = Mock(LdapManager) + ldapManager.deleteConfiguration(_) >> new LdapConfigurationResponse("localhost") + def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) + when: "LdapDeleteConfigurationCmd is executed" + ldapDeleteConfigurationCmd.execute() + then: "The given configuration should be deleted and returned" + ldapDeleteConfigurationCmd.responseObject.hostname == "localhost" + } + + def "Test successful return of getCommandName"() { + given: "We have an LdapManager and LdapDeleteConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) + when: "Get Command name is called" + String commandName = ldapDeleteConfigurationCmd.getCommandName() + then: "ldapconfigurationresponse is returned" + commandName == "ldapconfigurationresponse" + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListConfigurationCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListConfigurationCmdSpec.groovy new file mode 100644 index 00000000000..cd8b2a3ddf5 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListConfigurationCmdSpec.groovy @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LdapListConfigurationCmd +import org.apache.cloudstack.api.response.LdapConfigurationResponse +import org.apache.cloudstack.ldap.LdapConfigurationVO +import org.apache.cloudstack.ldap.LdapManager + +import com.cloud.utils.Pair + +class LdapListConfigurationCmdSpec extends spock.lang.Specification { + + def "Test failed response from execute"() { + given: "We have an LdapManager and a LdapListConfigurationsCmd" + def ldapManager = Mock(LdapManager) + List ldapConfigurationList = new ArrayList() + Pair, Integer> ldapConfigurations = new Pair, Integer>(); + ldapConfigurations.set(ldapConfigurationList, ldapConfigurationList.size()) + ldapManager.listConfigurations(_) >> ldapConfigurations + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "LdapListConfigurationCmd is executed" + ldapListConfigurationCmd.execute() + then: "Its response object contains an array that is 0" + ldapListConfigurationCmd.getResponseObject().getResponses().size() == 0 + } + + def "Test getEntityOwnerId is 1"() { + given: "We have an LdapManager and ListLdapConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "Get entity owner id is called" + long ownerId = ldapListConfigurationCmd.getEntityOwnerId() + then: "a 1 is returned" + ownerId == 1 + } + + def "Test successful response from execute"() { + given: "We have an LdapManager with a configuration and a LdapListConfigurationsCmd" + def ldapManager = Mock(LdapManager) + List ldapConfigurationList = new ArrayList() + ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389)) + Pair, Integer> ldapConfigurations = new Pair, Integer>(); + ldapConfigurations.set(ldapConfigurationList, ldapConfigurationList.size()) + ldapManager.listConfigurations(_) >> ldapConfigurations + ldapManager.createLdapConfigurationResponse(_) >> new LdapConfigurationResponse("localhost", 389) + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "LdapListConfigurationsCmd is executed" + ldapListConfigurationCmd.execute() + then: "Its response object contains an array that is not 0 in size" + ldapListConfigurationCmd.getResponseObject().getResponses().size() != 0 + } + + def "Test successful return of getCommandName"() { + given: "We have an LdapManager and LdapListConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "Get command name is called" + String commandName = ldapListConfigurationCmd.getCommandName() + then: "ldapconfigurationresponse is returned" + commandName == "ldapconfigurationresponse" + } + + def "Test successful setting of hostname"() { + given: "We have an LdapManager and LdapListConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "The hostname is set" + ldapListConfigurationCmd.setHostname("localhost") + then: "Get hostname returns the set value" + ldapListConfigurationCmd.getHostname() == "localhost" + } + + def "Test successful setting of Port"() { + given: "We have an LdapManager and LdapListConfigurationCmd" + def ldapManager = Mock(LdapManager) + def ldapListConfigurationCmd = new LdapListConfigurationCmd(ldapManager) + when: "The port is set" + ldapListConfigurationCmd.setPort(389) + then: "Get port returns the set value" + ldapListConfigurationCmd.getPort() == 389 + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListUsersCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListUsersCmdSpec.groovy new file mode 100644 index 00000000000..5039443c6ef --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapListUsersCmdSpec.groovy @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.api.command.LdapListUsersCmd +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.admin.user.ListUsersCmd +import org.apache.cloudstack.api.response.LdapUserResponse +import org.apache.cloudstack.api.response.ListResponse +import org.apache.cloudstack.api.response.UserResponse +import org.apache.cloudstack.ldap.LdapManager +import org.apache.cloudstack.ldap.LdapUser +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException +import org.apache.cloudstack.query.QueryService + +class LdapListUsersCmdSpec extends spock.lang.Specification { + def "Test getEntityOwnerId is 1"() { + given: "We have an LdapManager, QueryService and LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + def queryService = Mock(QueryService) + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService) + when: "Get entity owner id is called" + long ownerId = ldapListUsersCmd.getEntityOwnerId() + then: "a 1 should be returned" + ownerId == 1 + } + + def "Test successful empty response from execute"() { + given: "We have a LdapManager with no users, QueryService and a LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + ldapManager.getUsers() >> {throw new NoLdapUserMatchingQueryException()} + def queryService = Mock(QueryService) + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService) + when: "LdapListUsersCmd is executed" + ldapListUsersCmd.execute() + then: "An array of size 0 is returned" + ldapListUsersCmd.responseObject.getResponses().size() == 0 + } + + def "Test successful response from execute"() { + given: "We have an LdapManager, one user, QueryService and a LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + List users = new ArrayList() + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org")) + ldapManager.getUsers() >> users + LdapUserResponse response = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + ldapManager.createLdapUserResponse(_) >> response + def queryService = Mock(QueryService) + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService) + when: "LdapListUsersCmd is executed" + ldapListUsersCmd.execute() + then: "a list of size not 0 is returned" + ldapListUsersCmd.responseObject.getResponses().size() != 0 + } + + def "Test successful return of getCommandName"() { + given: "We have an LdapManager, QueryService and a LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + def queryService = Mock(QueryService) + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService) + when: "Get command name is called" + String commandName = ldapListUsersCmd.getCommandName() + then: "ldapuserresponse is returned" + commandName == "ldapuserresponse" + } + + def "Test successful result from isACloudstackUser"() { + given: "We have an LdapManager and a LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + def queryService = Mock(QueryService) + + UserResponse userResponse = new UserResponse() + userResponse.setUsername("rmurphy") + + ArrayList responses = new ArrayList() + responses.add(userResponse); + + ListResponse queryServiceResponse = new ListResponse() + queryServiceResponse.setResponses(responses) + + queryService.searchForUsers(_) >> queryServiceResponse + + def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager,queryService) + + when: "isACloudstackUser is executed" + def result = ldapListUsersCmd.isACloudstackUser(ldapUser); + + then: "The result is true" + result == true; + } + + def "Test failed result from isACloudstackUser"() { + given: "We have an LdapManager and a LdapListUsersCmd" + def ldapManager = Mock(LdapManager) + def queryService = Mock(QueryService) + + queryService.searchForUsers(_) >> new ListResponse() + + def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + def ldapListUsersCmd = new LdapListUsersCmd(ldapManager,queryService) + + when: "isACloudstackUser is executed" + def result = ldapListUsersCmd.isACloudstackUser(ldapUser); + + then: "The result is true" + result == false; + } +} 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 new file mode 100644 index 00000000000..d681eace90a --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy @@ -0,0 +1,336 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import javax.naming.NamingException +import javax.naming.ldap.InitialLdapContext + +import org.apache.cloudstack.api.command.LdapListConfigurationCmd +import org.apache.cloudstack.ldap.* +import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl + +import com.cloud.exception.InvalidParameterValueException +import com.cloud.utils.Pair + +class LdapManagerImplSpec extends spock.lang.Specification { + def "Test failing of getUser due to bind issue"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext() >> { throw new NamingException() } + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for a user but there is a bind issue" + ldapManager.getUser("rmurphy") + then: "an exception is thrown" + thrown NamingException + } + + def "Test failing of getUsers due to bind issue"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext() >> { throw new NamingException() } + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for a group of users but there is a bind issue" + ldapManager.getUsers() + then: "An exception is thrown" + thrown NoLdapUserMatchingQueryException + } + + def "Test failing of searchUsers due to a failure to bind"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext() >> { throw new NamingException() } + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for users" + ldapManager.searchUsers("rmurphy") + then: "An exception is thrown" + thrown NoLdapUserMatchingQueryException + } + + def "Test LdapConfigurationResponse generation"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A ldap configuration response is generated" + def result = ldapManager.createLdapConfigurationResponse(new LdapConfigurationVO("localhost", 389)) + then: "the result of the response should match the given LdapConfigurationVO" + result.hostname == "localhost" + result.port == 389 + } + + def "Test LdapUserResponse generation"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A ldap user response is generated" + def result = ldapManager.createLdapUserResponse(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org")) + then: "The result of the response should match the given ldap user" + result.username == "rmurphy" + result.email == "rmurphy@test.com" + result.firstname == "Ryan" + result.lastname == "Murphy" + result.principal == "cn=rmurphy,dc=cloudstack,dc=org" + } + + def "Test success getUsers"() { + 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")) + ldapUserManager.getUsers(_) >> users; + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for a group of users" + def result = ldapManager.getUsers() + then: "A list greater than 0 is returned" + result.size() > 0; + } + + def "Test success of getUser"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext() >> null + ldapUserManager.getUser(_, _) >> new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for a user" + def result = ldapManager.getUser("rmurphy") + then: "The user is returned" + result.username == "rmurphy" + result.email == "rmurphy@test.com" + result.firstname == "Ryan" + result.lastname == "Murphy" + result.principal == "cn=rmurphy,dc=cloudstack,dc=org" + } + + def "Test successful closing of context"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "The context is closed" + def context = Mock(InitialLdapContext) + ldapManager.closeContext(context) + then: "The context is null" + context.defaultInitCtx == null + } + + def "Test successful failed result from canAuthenticate due to bad password"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + ldapContextFactory.createUserContext(_, _) >> { throw new NamingException() } + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager]) + ldapManager.getUser(_) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") } + when: "The user attempts to authenticate with a bad password" + def result = ldapManager.canAuthenticate("rmurphy", "password") + then: "The authentication fails" + result == false + } + + def "Test successful failed result from canAuthenticate due to user not found"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager]) + ldapManager.getUser(_) >> { throw new NamingException() } + when: "The user attempts to authenticate and the user is not found" + def result = ldapManager.canAuthenticate("rmurphy", "password") + then: "the authentication fails" + result == false + } + + def "Test successful failed result from deleteConfiguration due to configuration not existing"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapConfigurationDao.findByHostname(_) >> null + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A ldap configuration that doesn't exist is deleted" + ldapManager.deleteConfiguration("localhost") + then: "A exception is thrown" + thrown InvalidParameterValueException + } + + def "Test successful failing to close of context"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "The context is closed" + def context = Mock(InitialLdapContext) + context.close() >> { throw new NamingException() } + ldapManager.closeContext(context) + then: "An exception is thrown" + context.defaultInitCtx == null + } + + def "Test successful result from canAuthenticate"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + ldapContextFactory.createUserContext(_, _) >> null + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager]) + ldapManager.getUser(_) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") } + when: "A user authenticates" + def result = ldapManager.canAuthenticate("rmurphy", "password") + then: "The result is true" + result == true + } + + def "Test successful result from deleteConfiguration"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapConfigurationDao.findByHostname(_) >> { + def configuration = new LdapConfigurationVO("localhost", 389) + configuration.setId(0); + return configuration; + } + ldapConfigurationDao.remove(_) >> null + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A ldap configuration is deleted" + def result = ldapManager.deleteConfiguration("localhost") + then: "The deleted configuration is returned" + result.hostname == "localhost" + result.port == 389 + } + + def "Test successful result from searchUsers"() { + 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")) + ldapUserManager.getUsers(_, _) >> users; + + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "We search for users" + def result = ldapManager.searchUsers("rmurphy"); + then: "A list of atleast 1 is returned" + result.size() > 0; + } + + def "Test successfully addConfiguration"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext(_) >> null + ldapConfigurationDao.persist(_) >> null + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A ldap configuration is added" + def result = ldapManager.addConfiguration("localhost", 389) + then: "the resulting object contain the given hostname and port" + result.hostname == "localhost" + result.port == 389 + } + + def "Test that addConfiguration fails when a binding fails"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapContextFactory.createBindContext(_) >> { throw new NamingException() } + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A configuration is added that can not be binded" + ldapManager.addConfiguration("localhost", 389) + then: "An exception is thrown" + thrown InvalidParameterValueException + } + + def "Test that addConfiguration fails when a duplicate configuration exists"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + ldapConfigurationDao.findByHostname(_) >> new LdapConfigurationVO("localhost", 389) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "a configuration that already exists is added" + ldapManager.addConfiguration("localhost", 389) + then: "An exception is thrown" + thrown InvalidParameterValueException + } + + def "Test that getCommands isn't empty"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "Get commands is called" + def result = ldapManager.getCommands() + then: "it must have atleast 1 command" + result.size() > 0 + } + + def "Testing of listConfigurations"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + List ldapConfigurationList = new ArrayList() + ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389)) + Pair, Integer> configurations = new Pair, Integer>(); + configurations.set(ldapConfigurationList, ldapConfigurationList.size()) + ldapConfigurationDao.searchConfigurations(_, _) >> configurations + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A request for configurations is made" + def result = ldapManager.listConfigurations(new LdapListConfigurationCmd()) + then: "Then atleast 1 ldap configuration is returned" + result.second() > 0 + } + + def "Testing of isLdapEnabled"() { + given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager" + def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl) + def ldapContextFactory = Mock(LdapContextFactory) + def ldapUserManager = Mock(LdapUserManager) + List ldapConfigurationList = new ArrayList() + ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389)) + Pair, Integer> configurations = new Pair, Integer>(); + configurations.set(ldapConfigurationList, ldapConfigurationList.size()) + ldapConfigurationDao.searchConfigurations(_, _) >> configurations + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + when: "A request to find out is ldap enabled" + def result = ldapManager.isLdapEnabled(); + then: "true is returned because a configuration was found" + result == true; + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapSearchUserCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapSearchUserCmdSpec.groovy new file mode 100644 index 00000000000..fce299d933d --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapSearchUserCmdSpec.groovy @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LdapUserSearchCmd +import org.apache.cloudstack.api.response.LdapUserResponse +import org.apache.cloudstack.ldap.LdapManager +import org.apache.cloudstack.ldap.LdapUser +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException + +class LdapSearchUserCmdSpec extends spock.lang.Specification { + def "Test getEntityOwnerId is 1"() { + given: "We have an Ldap manager and ldap user search cmd" + def ldapManager = Mock(LdapManager) + def ldapUserSearchCmd = new LdapUserSearchCmd(ldapManager) + when: "getEntityOwnerId is called" + long ownerId = ldapUserSearchCmd.getEntityOwnerId() + then: "1 is returned" + ownerId == 1 + } + + def "Test successful empty response from execute"() { + given: "We have an Ldap manager and ldap user search cmd" + def ldapManager = Mock(LdapManager) + ldapManager.searchUsers(_) >> {throw new NoLdapUserMatchingQueryException()} + def ldapUserSearchCmd = new LdapUserSearchCmd(ldapManager) + when: "The command is executed with no users found" + ldapUserSearchCmd.execute() + then: "An empty array is returned" + ldapUserSearchCmd.responseObject.getResponses().size() == 0 + } + + def "Test successful response from execute"() { + given: "We have an Ldap manager and ldap user search cmd" + def ldapManager = Mock(LdapManager) + List users = new ArrayList() + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org")) + ldapManager.searchUsers(_) >> users + LdapUserResponse response = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org") + ldapManager.createLdapUserResponse(_) >> response + def ldapUserSearchCmd = new LdapUserSearchCmd(ldapManager) + when: "The command is executed" + ldapUserSearchCmd.execute() + then: "A array with length of atleast 1 is returned" + ldapUserSearchCmd.responseObject.getResponses().size() > 0 + } + + def "Test successful return of getCommandName"() { + given: "We have an Ldap manager and ldap user search cmd" + def ldapManager = Mock(LdapManager) + def ldapUserSearchCmd = new LdapUserSearchCmd(ldapManager) + when: "When a request for the command name is made" + String commandName = ldapUserSearchCmd.getCommandName() + then: "ldapuserresponse is returned" + commandName == "ldapuserresponse" + } +} 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 new file mode 100644 index 00000000000..339923e57c0 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerSpec.groovy @@ -0,0 +1,206 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.LdapConfiguration +import org.apache.cloudstack.ldap.LdapUserManager +import spock.lang.Shared + +import javax.naming.NamingException +import javax.naming.directory.Attribute +import javax.naming.directory.Attributes +import javax.naming.directory.SearchControls +import javax.naming.directory.SearchResult +import javax.naming.ldap.LdapContext + +class LdapUserManagerSpec extends spock.lang.Specification { + + @Shared + private def ldapConfiguration + + @Shared + private def username + + @Shared + private def email + + @Shared + private def firstname + + @Shared + private def lastname + + @Shared + private def principal + + private def createContext() { + Attributes attributes = createUserAttributes(username, email, firstname, lastname) + SearchResult searchResults = createSearchResult(attributes) + def searchUsersResults = new BasicNamingEnumerationImpl() + searchUsersResults.add(searchResults); + + def context = Mock(LdapContext) + context.search(_, _, _) >> searchUsersResults; + + return context + } + + private SearchResult createSearchResult(attributes) { + def search = Mock(SearchResult) + + search.getName() >> "cn=" + attributes.getAt("uid").get(); + + search.getAttributes() >> attributes + + return search + } + + private Attributes createUserAttributes(String username, String email, String firstname, String lastname) { + def attributes = Mock(Attributes) + + def nameAttribute = Mock(Attribute) + nameAttribute.getId() >> "uid" + nameAttribute.get() >> username + attributes.get("uid") >> nameAttribute + + def mailAttribute = Mock(Attribute) + mailAttribute.getId() >> "mail" + mailAttribute.get() >> email + attributes.get("mail") >> mailAttribute + + def givennameAttribute = Mock(Attribute) + givennameAttribute.getId() >> "givenname" + givennameAttribute.get() >> firstname + attributes.get("givenname") >> givennameAttribute + + def snAttribute = Mock(Attribute) + snAttribute.getId() >> "sn" + snAttribute.get() >> lastname + attributes.get("sn") >> snAttribute + + return attributes + } + + def setupSpec() { + ldapConfiguration = Mock(LdapConfiguration) + + ldapConfiguration.getScope() >> SearchControls.SUBTREE_SCOPE + ldapConfiguration.getReturnAttributes() >> ["uid", "mail", "cn"] + ldapConfiguration.getUsernameAttribute() >> "uid" + ldapConfiguration.getEmailAttribute() >> "mail" + ldapConfiguration.getFirstnameAttribute() >> "givenname" + ldapConfiguration.getLastnameAttribute() >> "sn" + ldapConfiguration.getBaseDn() >> "dc=cloudstack,dc=org" + + username = "rmurphy" + email = "rmurphy@test.com" + firstname = "Ryan" + lastname = "Murphy" + principal = "cn=" + username + "," + ldapConfiguration.getBaseDn() + } + + def "Test successfully creating an Ldap User from Search result"() { + given: "We have attributes, a search and a user manager" + def attributes = createUserAttributes(username, email, firstname, lastname) + def search = createSearchResult(attributes) + def userManager = new LdapUserManager(ldapConfiguration) + def result = userManager.createUser(search) + + expect: "The crated user the data supplied from LDAP" + + result.username == username + result.email == email + result.firstname == firstname + result.lastname == lastname + result.principal == principal + } + + def "Test successfully returning a list from get users"() { + given: "We have a LdapUserManager" + + def userManager = new LdapUserManager(ldapConfiguration) + + when: "A request for users is made" + def result = userManager.getUsers(username, createContext()) + + then: "A list of users is returned" + result.size() == 1 + } + + def "Test successfully returning a list from get users when no username is given"() { + given: "We have a LdapUserManager" + + def userManager = new LdapUserManager(ldapConfiguration) + + when: "Get users is called without a username" + def result = userManager.getUsers(createContext()) + + then: "All users are returned" + result.size() == 1 + } + + def "Test successfully returning a NamingEnumeration from searchUsers"() { + given: "We have a LdapUserManager" + def userManager = new LdapUserManager(ldapConfiguration) + + when: "We search for users" + def result = userManager.searchUsers(createContext()) + + then: "A list of users are returned." + result.next().getName() + "," + ldapConfiguration.getBaseDn() == principal + } + + def "Test successfully returning an Ldap user from a get user request"() { + given: "We have a LdapUserMaanger" + + def userManager = new LdapUserManager(ldapConfiguration) + + when: "A request for a user is made" + def result = userManager.getUser(username, createContext()) + + then: "The user is returned" + result.username == username + result.email == email + result.firstname == firstname + result.lastname == lastname + result.principal == principal + } + + def "Test successfully throwing an exception when no users are found with getUser"() { + given: "We have a seachResult of users and a User Manager" + + def searchUsersResults = new BasicNamingEnumerationImpl() + + def context = Mock(LdapContext) + context.search(_, _, _) >> searchUsersResults; + + def userManager = new LdapUserManager(ldapConfiguration) + + when: "a get user request is made and no user is found" + def result = userManager.getUser(username, context) + + then: "An exception is thrown." + thrown NamingException + } + + def "Test that a newly created Ldap User Manager is not null"() { + given: "You have created a new Ldap user manager object" + def result = new LdapUserManager(); + expect: "The result is not null" + result != null + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserResponseSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserResponseSpec.groovy new file mode 100644 index 00000000000..f1978fa60d2 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserResponseSpec.groovy @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.api.response.LdapUserResponse + + +class LdapUserResponseSpec extends spock.lang.Specification { + def "Testing succcessful setting of LdapUserResponse email"() { + given: "We have an LdapResponse" + LdapUserResponse response = new LdapUserResponse(); + when: "An email address is set" + response.setEmail("rmurphy@test.com"); + then: "Get email should return that set email" + response.getEmail() == "rmurphy@test.com"; + } + + def "Testing successful setting of LdapUserResponse firstname"() { + given: "We have an LdapUserResponse" + LdapUserResponse response = new LdapUserResponse() + when: "A firstname is set" + response.setFirstname("Ryan") + then: "gGet Firstname returns the set value" + response.getFirstname() == "Ryan" + } + + def "Testing successful setting of LdapUserResponse lastname"() { + given: "We have an LdapUserResponse" + LdapUserResponse response = new LdapUserResponse() + when: "A lastname is set" + response.setLastname("Murphy") + then: "Get lastname is returned" + response.getLastname() == "Murphy" + } + + def "Testing successful setting of LdapUserResponse principal"() { + given: "We have an LdapResponse" + LdapUserResponse response = new LdapUserResponse() + when: "A principal is set" + response.setPrincipal("dc=cloudstack,dc=org") + then: "Get principled returns the set value" + response.getPrincipal() == "dc=cloudstack,dc=org" + } + + def "Testing successful setting of LdapUserResponse username"() { + given: "We have an LdapUserResponse" + LdapUserResponse response = new LdapUserResponse() + when: "A username is set" + response.setUsername("rmurphy") + then: "Get username returns the set value." + response.getUsername() == "rmurphy" + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy new file mode 100644 index 00000000000..8fd1ccc680e --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.LdapUser + +class LdapUserSpec extends spock.lang.Specification { + + def "Testing LdapUsers hashCode generation"() { + given: + def userA = new LdapUser(usernameA, "", "", "", "") + expect: + userA.hashCode() == usernameA.hashCode() + where: + usernameA = "A" + } + + def "Testing that LdapUser successfully gives the correct result for a compare to"() { + given: "You have created two LDAP user objects" + def userA = new LdapUser(usernameA, "", "", "", "") + def userB = new LdapUser(usernameB, "", "", "", "") + expect: "That when compared the result is less than or equal to 0" + userA.compareTo(userB) <= 0 + where: "The following values are used" + usernameA | usernameB + "A" | "B" + "A" | "A" + } + + def "Testing that LdapUsers equality"() { + given: + def userA = new LdapUser(usernameA, "", "", "", "") + def userB = new LdapUser(usernameB, "", "", "", "") + expect: + userA.equals(userA) == true + userA.equals(new Object()) == false + userA.equals(userB) == false + where: + usernameA | usernameB + "A" | "B" + } + + def "Testing that the username is correctly set with the ldap object"() { + given: "You have created a LDAP user object with a username" + def user = new LdapUser(username, "", "", "","") + expect: "The username is equal to the given data source" + user.getUsername() == username + where: "The username is set to " + username << ["", null, "rmurphy"] + } + + def "Testing the email is correctly set with the ldap object"() { + given: "You have created a LDAP user object with a email" + def user = new LdapUser("", email, "", "","") + expect: "The email is equal to the given data source" + user.getEmail() == email + where: "The email is set to " + email << ["", null, "test@test.com"] + } + + def "Testing the firstname is correctly set with the ldap object"() { + given: "You have created a LDAP user object with a firstname" + def user = new LdapUser("", "", firstname, "", "") + expect: "The firstname is equal to the given data source" + user.getFirstname() == firstname + where: "The firstname is set to " + firstname << ["", null, "Ryan"] + } + + def "Testing the lastname is correctly set with the ldap object"() { + given: "You have created a LDAP user object with a lastname" + def user = new LdapUser("", "", "", lastname, "") + expect: "The lastname is equal to the given data source" + user.getLastname() == lastname + where: "The lastname is set to " + lastname << ["", null, "Murphy"] + } + + def "Testing the principal is correctly set with the ldap object"() { + given: "You have created a LDAP user object with a principal" + def user = new LdapUser("", "", "", "", principal) + expect: "The principal is equal to the given data source" + user.getPrincipal() == principal + where: "The username is set to " + principal << ["", null, "cn=rmurphy,dc=cloudstack,dc=org"] + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUtilsSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUtilsSpec.groovy new file mode 100644 index 00000000000..984d2b3f801 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUtilsSpec.groovy @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.LdapUtils + +import javax.naming.directory.Attribute +import javax.naming.directory.Attributes + +class LdapUtilsSpec extends spock.lang.Specification { + def "Testing than an attribute is not successfully returned"() { + given: "You have an attributes object with some attribute" + def attributes = Mock(Attributes) + attributes.get("uid") >> null + + when: "You get the attribute" + String foundValue = LdapUtils.getAttributeValue(attributes, "uid") + + then: "Its value equals uid" + foundValue == null + } + + def "Testing than an attribute is successfully returned"() { + given: "You have an attributes object with some attribute" + def attributes = Mock(Attributes) + def attribute = Mock(Attribute) + attribute.getId() >> name + attribute.get() >> value + attributes.get(name) >> attribute + + when: "You get the attribute" + String foundValue = LdapUtils.getAttributeValue(attributes, name) + + then: "Its value equals uid" + foundValue == value + + where: + name | value + "uid" | "rmurphy" + "email" | "rmurphy@test.com" + } + + def "Testing that a Ldap Search Filter is correctly escaped"() { + given: "You have some input from a user" + + expect: "That the input is escaped" + LdapUtils.escapeLDAPSearchFilter(input) == result + + where: "The following inputs are given " + input | result + "Hi This is a test #çà" | "Hi This is a test #çà" + "Hi (This) = is * a \\ test # ç à ô \u0000" | "Hi \\28This\\29 = is \\2a a \\5c test # ç à ô \\00" + } +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy new file mode 100644 index 00000000000..4c0cc4b688f --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException + +class NoLdapUserMatchingQueryExceptionSpec extends spock.lang.Specification { + def "Test that the query is correctly set within the No LDAP user matching query exception object"() { + given: "You have created an No LDAP user matching query exception object with a query set" + def exception = new NoLdapUserMatchingQueryException(query) + expect: "The username is equal to the given data source" + exception.getQuery() == query + where: "The username is set to " + query << ["", null, "murp*"] + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoSuchLdapUserExceptionSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoSuchLdapUserExceptionSpec.groovy new file mode 100644 index 00000000000..dbdf646721d --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/NoSuchLdapUserExceptionSpec.groovy @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.NoSuchLdapUserException; + +class NoSuchLdapUserExceptionSpec extends spock.lang.Specification { + def "Test that the username is correctly set within the No such LDAP user exception object"() { + given: "You have created an No such LDAP user exception object with the username set" + def exception = new NoSuchLdapUserException(username) + expect: "The username is equal to the given data source" + exception.getUsername() == username + where: "The username is set to " + username << ["", null, "rmurphy"] + } +} diff --git a/plugins/user-authenticators/ldap/test/resources/cloudstack.org.ldif b/plugins/user-authenticators/ldap/test/resources/cloudstack.org.ldif new file mode 100644 index 00000000000..d5f5c24ed93 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/resources/cloudstack.org.ldif @@ -0,0 +1,295 @@ +version: 1 + +dn: dc=cloudstack,dc=org +objectClass: dcObject +objectClass: organization +dc: cloudstack +o: cloudstack + +dn: cn=Ryan Murphy,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Ryan Murphy +sn: Murphy +givenName: Ryan +mail: rmurphy@cloudstack.org +uid: rmurphy +userpassword:: cGFzc3dvcmQ= + +dn: cn=Barbara Brewer,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Barbara Brewer +sn: Brewer +mail: bbrewer@cloudstack.org +uid: bbrewer +userpassword:: cGFzc3dvcmQ= + +dn: cn=Zak Wilkinson,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Zak Wilkinson +givenname: Zak +sn: Wilkinson +uid: zwilkinson +userpassword:: cGFzc3dvcmQ= + +dn: cn=Archie Shingleton,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Archie Shingleton +sn: Shingleton +givenName: Archie +mail: ashingleton@cloudstack.org +uid: ashingleton +userpassword:: cGFzc3dvcmQ= + +dn: cn=Cletus Pears,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Cletus Pears +sn: Pears +givenName: Cletus +mail: cpears@cloudstack.org +uid: cpears +userpassword:: cGFzc3dvcmQ= + +dn: cn=Teisha Milewski,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Teisha Milewski +sn: Milewski +givenName: Teisha +mail: tmilewski@cloudstack.org +uid: tmilewski +userpassword:: cGFzc3dvcmQ= + +dn: cn=Eloy Para,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Eloy Para +sn: Para +givenName: Eloy +mail: epara@cloudstack.org +uid: epara +userpassword:: cGFzc3dvcmQ= + +dn: cn=Elaine Lamb,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Elaine Lamb +sn: Lamb +givenName: Elaine +mail: elamb@cloudstack.org +uid: elamb +userpassword:: cGFzc3dvcmQ= + +dn: cn=Soon Griffen,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Soon Griffen +sn: Griffen +givenName: Soon +mail: sgriffen@cloudstack.org +uid: sgriffen +userpassword:: cGFzc3dvcmQ= + +dn: cn=Tran Neisler,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Tran Neisler +sn: Neisler +givenName: Tran +mail: tneisler@cloudstack.org +uid: tneisler +userpassword:: cGFzc3dvcmQ= + +dn: cn=Mirella Zeck,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Mirella Zeck +sn: Zeck +givenName: Mirella +mail: mzeck@cloudstack.org +uid: mzeck +userpassword:: cGFzc3dvcmQ= + +dn: cn=Greg Hoskin,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Greg Hoskin +sn: Hoskin +givenName: Greg +mail: ghoskin@cloudstack.org +uid: ghoskin +userpassword:: cGFzc3dvcmQ= + +dn: cn=Johanne Runyon,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Johanne Runyon +sn: Runyon +givenName: Johanne +mail: jrunyon@cloudstack.org +uid: jrunyon +userpassword:: cGFzc3dvcmQ= + +dn: cn=Mabelle Waiters,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Mabelle Waiters +sn: Waiters +givenName: Mabelle +mail: mwaiters@cloudstack.org +uid: mwaiters +userpassword:: cGFzc3dvcmQ= + +dn: cn=Phillip Fruge,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Phillip Fruge +sn: Fruge +givenName: Phillip +mail: pfruge@cloudstack.org +uid: pfruge +userpassword:: cGFzc3dvcmQ= + +dn: cn=Jayna Ridenhour,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Jayna Ridenhour +sn: Ridenhour +givenName: Jayna +mail: jridenhour@cloudstack.org +uid: jridenhour +userpassword:: cGFzc3dvcmQ= + +dn: cn=Marlyn Mandujano,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Marlyn Mandujano +sn: Mandujano +givenName: Marlyn +mail: mmandujano@cloudstack.org +uid: mmandujano +userpassword:: cGFzc3dvcmQ= + +dn: cn=Shaunna Scherer,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Shaunna Scherer +sn: Scherer +givenName: Shaunna +mail: sscherer@cloudstack.org +uid: sscherer +userpassword:: cGFzc3dvcmQ= + +dn: cn=Adriana Bozek,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Adriana Bozek +sn: Bozek +givenName: Adriana +mail: abozek@cloudstack.org +uid: abozek +userpassword:: cGFzc3dvcmQ= + +dn: cn=Silvana Chipman,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Silvana Chipman +sn: Chipman +givenName: Silvana +mail: schipman@cloudstack.org +uid: schipman +userpassword:: cGFzc3dvcmQ= + +dn: cn=Marion Wasden,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Marion Wasden +sn: Wasden +givenName: Marion +mail: mwasden@cloudstack.org +uid: mwasden +userpassword:: cGFzc3dvcmQ= + +dn: cn=Anisa Casson,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Anisa Casson +sn: Casson +givenName: Anisa +mail: acasson@cloudstack.org +uid: acasson +userpassword:: cGFzc3dvcmQ= + +dn: cn=Noel King,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Noel King +sn: King +givenName: Noel +mail: nking@cloudstack.org +uid: nking +userpassword:: cGFzc3dvcmQ= + + +dn: cn=Cammy Petri,dc=cloudstack,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Cammy Petri +sn: Petri +givenName: Cammy +mail: cpetri@cloudstack.org +uid: cpetri +userpassword:: cGFzc3dvcmQ= + diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 2f8fdbc94ac..5e015c69176 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -81,7 +81,6 @@ import org.apache.cloudstack.api.response.LBHealthCheckPolicyResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse; import org.apache.cloudstack.api.response.LBStickinessPolicyResponse; import org.apache.cloudstack.api.response.LBStickinessResponse; -import org.apache.cloudstack.api.response.LDAPConfigResponse; import org.apache.cloudstack.api.response.LoadBalancerResponse; import org.apache.cloudstack.api.response.NetworkACLItemResponse; import org.apache.cloudstack.api.response.NetworkACLResponse; @@ -2804,20 +2803,6 @@ public class ApiResponseHelper implements ResponseGenerator { return hcResponse; } - @Override - public LDAPConfigResponse createLDAPConfigResponse(String hostname, Integer port, Boolean useSSL, String queryFilter, String searchBase, - String bindDN) { - LDAPConfigResponse lr = new LDAPConfigResponse(); - lr.setHostname(hostname); - lr.setPort(port.toString()); - lr.setUseSSL(useSSL.toString()); - lr.setQueryFilter(queryFilter); - lr.setBindDN(bindDN); - lr.setSearchBase(searchBase); - lr.setObjectName("ldapconfig"); - return lr; - } - @Override public StorageNetworkIpRangeResponse createStorageNetworkIpRangeResponse(StorageNetworkIpRange result) { StorageNetworkIpRangeResponse response = new StorageNetworkIpRangeResponse(); diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index d2f470cc701..f74df48c652 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -415,6 +415,19 @@ public enum Config { // object store S3EnableRRS("Advanced", ManagementServer.class, Boolean.class, "s3.rrs.enabled", "false", "enable s3 reduced redundancy storage", null), + // Ldap + LdapBasedn("Advanced", ManagementServer.class, String.class, "ldap.basedn", null, "Sets the basedn for LDAP", null), + LdapBindPassword("Advanced", ManagementServer.class, String.class, "ldap.bind.password", null, "Sets the bind password for LDAP", null), + LdapBindPrincipal("Advanced", ManagementServer.class, String.class, "ldap.bind.principal", null, "Sets the bind principal for LDAP", null), + LdapEmailAttribute("Advanced", ManagementServer.class, String.class, "ldap.email.attribute", "mail", "Sets the email attribute used within LDAP", null), + LdapFirstnameAttribute("Advanced", ManagementServer.class, String.class, "ldap.firstname.attribute", "givenname", "Sets the firstname attribute used within LDAP", null), + LdapLastnameAttribute("Advanced", ManagementServer.class, String.class, "ldap.lastname.attribute", "sn", "Sets the lastname attribute used within LDAP", null), + LdapUsernameAttribute("Advanced", ManagementServer.class, String.class, "ldap.username.attribute", "uid", "Sets the username attribute used within LDAP", null), + LdapUserObject("Advanced", ManagementServer.class, String.class, "ldap.user.object", "inetOrgPerson", "Sets the object type of users within LDAP", null), + 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), + // VMSnapshots VMSnapshotMax("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.max", "10", "Maximum vm snapshots for a vm", null), VMSnapshotCreateWait("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.create.wait", "1800", "In second, timeout for create vm snapshot", null), @@ -423,15 +436,15 @@ public enum Config { BlacklistedRoutes("Advanced", VpcManager.class, String.class, "blacklisted.routes", null, "Routes that are blacklisted, can not be used for Static Routes creation for the VPC Private Gateway", "routes", ConfigurationParameterScope.zone.toString()), - + InternalLbVmServiceOfferingId("Advanced", ManagementServer.class, String.class, "internallbvm.service.offering", null, "Uuid of the service offering used by internal lb vm; if NULL - default system internal lb offering will be used", null), ExecuteInSequence("Advanced", ManagementServer.class, Boolean.class, "execute.in.sequence.hypervisor.commands", "false", "If set to true, StartCommand, StopCommand, CopyCommand will be synchronized on the agent side." + " If set to false, these commands become asynchronous. Default value is false.", null), ExecuteInSequenceNetworkElementCommands("Advanced", NetworkManager.class, Boolean.class, "execute.in.sequence.network.element.commands", "false", "If set to true, DhcpEntryCommand, SavePasswordCommand, UserDataCommand, VmDataCommand will be synchronized on the agent side." + " If set to false, these commands become asynchronous. Default value is false.", null), - + UCSSyncBladeInterval("Advanced", ManagementServer.class, Integer.class, "ucs.sync.blade.interval", "3600", "the interval cloudstack sync with UCS manager for available blades in case user remove blades from chassis without notifying CloudStack", null); - + private final String _category; private final Class _componentClass; private final Class _type; diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 555023719d8..2fdc9f2e9fd 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -41,10 +41,7 @@ import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import org.apache.cloudstack.acl.SecurityChecker; -import org.apache.cloudstack.api.ApiConstants.LDAPParams; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; @@ -1547,175 +1544,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } - @Override - @DB - public boolean removeLDAP(LDAPRemoveCmd cmd) { - _configDao.expunge(LDAPParams.hostname.toString()); - _configDao.expunge(LDAPParams.port.toString()); - _configDao.expunge(LDAPParams.queryfilter.toString()); - _configDao.expunge(LDAPParams.searchbase.toString()); - _configDao.expunge(LDAPParams.usessl.toString()); - _configDao.expunge(LDAPParams.dn.toString()); - _configDao.expunge(LDAPParams.passwd.toString()); - _configDao.expunge(LDAPParams.truststore.toString()); - _configDao.expunge(LDAPParams.truststorepass.toString()); - return true; - } - - @Override - @DB - public LDAPConfigCmd listLDAPConfig(LDAPConfigCmd cmd) { - String hostname = _configDao.getValue(LDAPParams.hostname.toString()); - cmd.setHostname(hostname == null ? "" : hostname); - String port = _configDao.getValue(LDAPParams.port.toString()); - cmd.setPort(port == null ? 0 : Integer.valueOf(port)); - String queryFilter = _configDao.getValue(LDAPParams.queryfilter.toString()); - cmd.setQueryFilter(queryFilter == null ? "" : queryFilter); - String searchBase = _configDao.getValue(LDAPParams.searchbase.toString()); - cmd.setSearchBase(searchBase == null ? "" : searchBase); - String useSSL = _configDao.getValue(LDAPParams.usessl.toString()); - cmd.setUseSSL(useSSL == null ? Boolean.FALSE : Boolean.valueOf(useSSL)); - String binddn = _configDao.getValue(LDAPParams.dn.toString()); - cmd.setBindDN(binddn == null ? "" : binddn); - String truststore = _configDao.getValue(LDAPParams.truststore.toString()); - cmd.setTrustStore(truststore == null ? "" : truststore); - return cmd; - } - - @Override - @DB - public boolean updateLDAP(LDAPConfigCmd cmd) { - try { - // set the ldap details in the zone details table with a zone id of - // -12 - String hostname = cmd.getHostname(); - Integer port = cmd.getPort(); - String queryFilter = cmd.getQueryFilter(); - String searchBase = cmd.getSearchBase(); - Boolean useSSL = cmd.getUseSSL(); - String bindDN = cmd.getBindDN(); - String bindPasswd = cmd.getBindPassword(); - String trustStore = cmd.getTrustStore(); - String trustStorePassword = cmd.getTrustStorePassword(); - - if (bindDN != null && bindPasswd == null) { - throw new InvalidParameterValueException( - "If you specify a bind name then you need to provide bind password too."); - } - - // check query filter if it contains valid substitution - if (!queryFilter.contains("%u") && !queryFilter.contains("%n") && !queryFilter.contains("%e")) { - throw new InvalidParameterValueException( - "QueryFilter should contain at least one of the substitutions: %u, %n or %e: " + queryFilter); - } - - // check if the info is correct - Hashtable env = new Hashtable(11); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - String protocol = "ldap://"; - if (useSSL) { - env.put(Context.SECURITY_PROTOCOL, "ssl"); - protocol = "ldaps://"; - if (trustStore == null || trustStorePassword == null) { - throw new InvalidParameterValueException( - "If you plan to use SSL then you need to configure the trust store."); - } - System.setProperty("javax.net.ssl.trustStore", trustStore); - System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); - } - env.put(Context.PROVIDER_URL, protocol + hostname + ":" + port); - if (bindDN != null && bindPasswd != null) { - env.put(Context.SECURITY_AUTHENTICATION, "simple"); - env.put(Context.SECURITY_PRINCIPAL, bindDN); - env.put(Context.SECURITY_CREDENTIALS, bindPasswd); - } - // Create the initial context - DirContext ctx = new InitialDirContext(env); - ctx.close(); - - // store the result in DB Configuration - ConfigurationVO cvo = _configDao.findByName(LDAPParams.hostname.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.hostname.toString(), - null, "Hostname or ip address of the ldap server eg: my.ldap.com"); - } - cvo.setValue(DBEncryptionUtil.encrypt(hostname)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.port.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.port.toString(), null, - "Specify the LDAP port if required, default is 389"); - } - cvo.setValue(DBEncryptionUtil.encrypt(port.toString())); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.queryfilter.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.queryfilter.toString(), - null, - "You specify a query filter here, which narrows down the users, who can be part of this domain"); - } - cvo.setValue(DBEncryptionUtil.encrypt(queryFilter)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.searchbase.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.searchbase.toString(), - null, - "The search base defines the starting point for the search in the directory tree Example: dc=cloud,dc=com."); - } - cvo.setValue(DBEncryptionUtil.encrypt(searchBase)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.usessl.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.usessl.toString(), null, - "Check Use SSL if the external LDAP server is configured for LDAP over SSL."); - } - cvo.setValue(DBEncryptionUtil.encrypt(useSSL.toString())); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.dn.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.dn.toString(), null, - "Specify the distinguished name of a user with the search permission on the directory"); - } - cvo.setValue(DBEncryptionUtil.encrypt(bindDN)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.passwd.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.passwd.toString(), null, - "Enter the password"); - } - cvo.setValue(DBEncryptionUtil.encrypt(bindPasswd)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.truststore.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", LDAPParams.truststore.toString(), - null, "Enter the path to trusted keystore"); - } - cvo.setValue(DBEncryptionUtil.encrypt(trustStore)); - _configDao.persist(cvo); - - cvo = _configDao.findByName(LDAPParams.truststorepass.toString()); - if (cvo == null) { - cvo = new ConfigurationVO("Hidden", "DEFAULT", "management-server", - LDAPParams.truststorepass.toString(), null, "Enter the password for trusted keystore"); - } - cvo.setValue(DBEncryptionUtil.encrypt(trustStorePassword)); - _configDao.persist(cvo); - - s_logger.debug("The ldap server is configured: " + hostname); - } catch (NamingException ne) { - throw new InvalidParameterValueException("Naming Exception, check you ldap data ! " + ne.getMessage() - + (ne.getCause() != null ? ("; Caused by:" + ne.getCause().getMessage()) : "")); - } - return true; - } - @Override @DB @ActionEvent(eventType = EventTypes.EVENT_ZONE_EDIT, eventDescription = "editing zone", async = false) diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 07d193ecc14..069a1d819de 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -90,8 +90,6 @@ import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd; import org.apache.cloudstack.api.command.admin.internallb.StartInternalLBVMCmd; import org.apache.cloudstack.api.command.admin.internallb.StopInternalLBVMCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; import org.apache.cloudstack.api.command.admin.network.AddNetworkDeviceCmd; import org.apache.cloudstack.api.command.admin.network.AddNetworkServiceProviderCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; @@ -2521,8 +2519,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ReconnectHostCmd.class); cmdList.add(UpdateHostCmd.class); cmdList.add(UpdateHostPasswordCmd.class); - cmdList.add(LDAPConfigCmd.class); - cmdList.add(LDAPRemoveCmd.class); cmdList.add(AddNetworkDeviceCmd.class); cmdList.add(AddNetworkServiceProviderCmd.class); cmdList.add(CreateNetworkOfferingCmd.class); diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index 74211d4b48a..3ec146b9537 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -28,8 +28,6 @@ import javax.naming.NamingException; import org.springframework.stereotype.Component; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; -import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; @@ -318,33 +316,6 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu return null; } - /* (non-Javadoc) - * @see com.cloud.configuration.ConfigurationService#updateLDAP(org.apache.cloudstack.api.commands.LDAPConfigCmd) - */ - @Override - public boolean updateLDAP(LDAPConfigCmd cmd) throws NamingException { - // TODO Auto-generated method stub - return false; - } - - /* (non-Javadoc) - * @see com.cloud.configuration.ConfigurationService#removeLDAP(org.apache.cloudstack.api.commands.LDAPRemoveCmd) - */ - @Override - public boolean removeLDAP(LDAPRemoveCmd cmd) { - // TODO Auto-generated method stub - return false; - } - - /* (non-Javadoc) - * @see com.cloud.configuration.ConfigurationService#listLDAPConfig(org.apache.cloudstack.api.commands.LDAPConfigCmd) - */ - @Override - public LDAPConfigCmd listLDAPConfig(LDAPConfigCmd cmd) { - // TODO Auto-generated method stub - return null; - } - /* (non-Javadoc) * @see com.cloud.configuration.ConfigurationService#isOfferingForVpc(com.cloud.offering.NetworkOffering) */ @@ -576,4 +547,4 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu } -} +} \ No newline at end of file diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 6be91ea5da4..ad3076f9f37 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -2307,3 +2307,23 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Storage', 'DEFAULT', 'manage INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Storage', 'DEFAULT', 'management-server', 'storage.cache.replacement.interval', '86400', 'time interval between cache replacement threads (in seconds).'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT', 'management-server', 'vmware.nested.virtualization', 'false', 'When set to true this will enable nested virtualization when this is supported by the hypervisor'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.bind.principal', NULL, 'Specifies the bind principal to use for bind to LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.bind.password', NULL, 'Specifies the password to use for binding to LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.username.attribute', 'uid', 'Sets the username attribute used within LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.email.attribute', 'mail', 'Sets the email attribute used within LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.firstname.attribute', 'givenname', 'Sets the firstname attribute used within LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.lastname.attribute', 'sn', 'Sets the lastname attribute used within LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.user.object', 'inetOrgPerson', 'Sets the object type of users within LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.basedn', NULL, 'Sets the basedn for LDAP'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.search.group.principle', NULL, 'Sets the principle of the group that users must be a member of'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.truststore', NULL, 'Sets the path to the truststore to use for LDAP SSL'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ldap.truststore.password', NULL, 'Sets the password for the truststore'); + + +CREATE TABLE `cloud`.`ldap_configuration` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `hostname` varchar(255) NOT NULL COMMENT 'the hostname of the ldap server', + `port` int(10) COMMENT 'port that the ldap server is listening on', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + diff --git a/test/integration/component/test_ldap.py b/test/integration/component/test_ldap.py index fc3bd486266..83f970be27d 100644 --- a/test/integration/component/test_ldap.py +++ b/test/integration/component/test_ldap.py @@ -44,78 +44,30 @@ class Services: def __init__(self): self.services = { "account": { - "email": "test@test.com", - "firstname": "test", - "lastname": "t", - "username": "test", - "password": "password", + "email": "rmurphy@cloudstack.org", + "firstname": "Ryan", + "lastname": "Murphy", + "username": "rmurphy", + "password": "internalcloudstackpassword", }, - "ldapCon_1":#valid values&Query filter as email. + "ldapConfiguration_1": { - "ldapHostname": "10.147.38.163", - "port": "389", - "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", - "bindpass": "aaaa_1111", - "queryfilter": "(&(mail=%e))", - "searchbase": "CN=Users,DC=hyd-qa,DC=com", - "ldapusername": "test", - "ldappasswd": "aaaa_1111" - }, - "ldapCon_2": ##valid values&Query filter as displayName. - { - "ldapHostname": "10.147.38.163", - "port": "389", - "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", - "bindpass": "aaaa_1111", - "queryfilter": "(&(displayName=%u))", - "searchbase": "CN=Users,DC=hyd-qa,DC=com", - "ldapusername": "test", - "ldappasswd": "aaaa_1111" - }, - "ldapCon_3": #Configuration with missing parameters value(queryfilter) - { - "ldapHostname": "10.147.38.163", - "port": "389", - "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", - "bindpass": "aaaa_1111", - "queryfilter": "", - "searchbase": "CN=Users,DC=hyd-qa,DC=com", - "ldapusername": "test", - "ldappasswd": "aaaa_1111" - }, - - "ldapCon_4": #invalid configuration-wrong query filter - { - "ldapHostname": "10.147.38.163", - "port": "389", - "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", - "bindpass": "aaaa_1111", - "queryfilter": "(&(displayName=%p))", - "searchbase":"CN=Users,DC=hyd-qa,DC=com", - "ldapusername": "test", - "ldappasswd": "aaaa_1111" - }, - "ldapCon_5": #Configuration with invalid ldap credentials - { - "ldapHostname": "10.147.38.163", - "port": "389", - "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", - "bindpass": "aaaa_1111", - "queryfilter": "(&(displayName=%u))", - "searchbase": "CN=Users,DC=hyd-qa,DC=com", - "ldapusername": "test", - "ldappasswd": "aaaa" + "basedn": "dc=cloudstack,dc=org", + "emailAttribute": "mail", + "realnameAttribute": "cn", + "userObject": "inetOrgPerson", + "usernameAttribute": "uid", + "hostname": "localhost", + "port": "10389", + "ldapUsername": "rmurphy", + "ldapPassword": "password" } - - - } class TestLdap(cloudstackTestCase): """ - This test perform registering ldap configuration details in CS and create a user[ldap user] in CS - and validate user credentials against LDAP server:AD + This tests attempts to register a LDAP server and authenticate as an LDAP user. """ @classmethod @@ -134,8 +86,6 @@ class TestLdap(cloudstackTestCase): @classmethod def tearDownClass(cls): try: - #Cleanup resources used - #print "tear down class" cleanup_resources(cls.api_client, cls._cleanup) except Exception as tde: @@ -144,10 +94,10 @@ class TestLdap(cloudstackTestCase): def setUp(self): - self.apiclient = self.testClient.getApiClient() + self.apiClient = self.testClient.getApiClient() self.acct = createAccount.createAccountCmd() - self.acct.accounttype = 0 #We need a regular user. admins have accounttype=1 + self.acct.accounttype = 0 self.acct.firstname = self.services["account"]["firstname"] self.acct.lastname = self.services["account"]["lastname"] self.acct.password = self.services["account"]["password"] @@ -155,208 +105,153 @@ class TestLdap(cloudstackTestCase): self.acct.email = self.services["account"]["email"] self.acct.account = self.services["account"]["username"] self.acct.domainid = 1 - # mapping ldap user by creating same user in cloudstack - - self.acctRes = self.apiclient.createAccount(self.acct) + self.acctRes = self.apiClient.createAccount(self.acct) return def tearDown(self): try: - #Clean up, terminate the created accounts, domains etc - deleteAcct = deleteAccount.deleteAccountCmd() deleteAcct.id = self.acctRes.id acct_name=self.acctRes.name - self.apiclient.deleteAccount(deleteAcct) + self.apiClient.deleteAccount(deleteAcct) self.debug("Deleted the the following account name %s:" %acct_name) - #delete only if ldapconfig registered in CS - if(self.ldapconfRes): - deleteldapconfg=ldapRemove.ldapRemoveCmd() - res=self.apiclient.ldapRemove(deleteldapconfg) + if(self.ldapconfRes==1): + self._deleteLdapConfiguration(self.services["ldapConfiguration_1"]) except Exception as e: raise Exception("Warning: Exception during cleanup : %s" % e) return @attr(tags=["advanced", "basic"]) - def test_01_configLDAP(self): - ''' - This test is to verify ldapConfig API with valid values.(i.e query fileter as email) - ''' - # 1. This test covers ldapConfig & login API with valid ldap credentials.. - # require ldap configuration:ldapCon_1 + def test_01_addLdapConfiguration(self): + """ + This test configures LDAP and attempts to authenticate as a user. + """ + self.debug("start test") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_1"]) + self.ldapconfRes=self._addLdapConfiguration(self.services["ldapConfiguration_1"]) if(self.ldapconfRes==1): + self.debug("Ldap Configuration was succcessful") - self.debug("configure ldap successful") - - #validating the user credentials with ldap Server - loginRes = self.chkLogin(self.services["ldapCon_1"]["ldapusername"], self.services["ldapCon_1"]["ldappasswd"]) - self.assertEquals(loginRes,1,"ldap Authentication failed") + loginRes = self._checkLogin(self.services["ldapConfiguration_1"]["ldapUsername"],self.services["ldapConfiguration_1"]["ldapPassword"]) + self.debug(loginRes) + self.assertEquals(loginRes,1,"Ldap Authentication") else: self.debug("LDAP Configuration failed with exception") - self.assertEquals(self.ldapconfRes,1,"ldapConfig API failed") + self.assertEquals(self.ldapconfRes,1,"addLdapConfiguration failed") self.debug("end test") - @attr(tags=["advanced", "basic"]) - def test_02_configLDAP(self): - ''' - This test is to verify ldapConfig API with valid values.(i.e query fileter as displayName) - ''' - - # 1. This test covers ldapConfig & login API with valid ldap credentials. - # 2. require ldap configuration:ldapCon_2 - - self.debug("start test") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_2"]) - self.assertEquals(self.ldapconfRes,1,"ldapConfig API failed") - if(self.ldapconfRes==1): - self.debug("configure ldap successful") - #validating the user credentials with ldap Server - loginRes = self.chkLogin(self.services["ldapCon_2"]["ldapusername"], self.services["ldapCon_2"]["ldappasswd"]) - self.assertEquals(loginRes,1,"ldap Authentication failed") - else: - self.debug("LDAP Configuration failed with exception") - self.debug("end test") - - @attr(tags=["advanced", "basic"]) - def test_03_configLDAP(self): - - ''' - This test is to verify ldapConfig API with missing config parameters value(i.queryfilter) - ''' - - # 1. Issue ldapConfig API with no ldap config parameter value and check behavior - # 2. require ldap configuration:ldapCon_3 - - self.debug("start test...") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_3"]) - self.assertEquals(self.ldapconfRes,0,"LDAP configuration successful with invalid value.API failed") - self.debug("end test") - @attr(tags=["advanced", "basic"]) - def test_04_configLDAP(self): - ''' - This test is to verify ldapConfig API with invalid configuration values(by passing wrong query filter) - ''' - # 1. calling ldapConfig API with invalid query filter value and check behavior - # 2. require ldap configuration:ldapCon_4 - - self.debug("start test...") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_4"]) - self.assertEquals(self.ldapconfRes,0,"API failed") - - - @attr(tags=["advanced", "basic"]) - def test_05_configLDAP(self): - - ''' - This test is to verify login API functionality by passing wrong ldap credentials - ''' - # 1.This script first configure the ldap and validates the user credentials using login API - # 2. require ldap configuration:ldapCon_5 - - - self.debug("start test") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_5"]) - self.assertEquals(self.ldapconfRes,1,"API failed") - #validating the cloudstack user credentials with ldap Server - loginRes = self.chkLogin(self.services["ldapCon_5"]["ldapusername"], self.services["ldapCon_5"]["ldappasswd"]) - self.assertNotEqual(loginRes,1,"login API failed") - self.debug("end test") - - @attr(tags=["advanced", "basic"]) - def test_06_removeLDAP(self): - ''' - This test is to verify ldapRemove API functionality - ''' - # 1. This script fist configures ldap and removes the configured ldap values - # 2. require ldap configuration:ldapCon_1 - - - self.debug("start test") - self.ldapconfRes=self._testldapConfig(self.services["ldapCon_1"]) - if(self.ldapconfRes==1): - self.debug("ldap configured successfully") - deleteldapconfg=ldapRemove.ldapRemoveCmd() - res=self.apiclient.ldapRemove(deleteldapconfg) - self.debug("ldap removed successfully") - self.ldapconfRes=0 - else: - - self.debug("LDAP Configuration failed with exception") - self.assertEquals(self.ldapconfRes,0,"ldapconfig API failed") - self.debug("end test") - - def _testldapConfig(self,ldapSrvD): + def _addLdapConfiguration(self,ldapConfiguration): """ - :param ldapSrvD - + :param ldapConfiguration """ - #This Method takes dictionary as parameter, - # reads the ldap configuration values from the passed dictionary and - # register the ldapconfig detail in cloudstack - # & return true or false based on ldapconfig API response - self.debug("start ldapconfig test") - #creating the ldapconfig cmd object - lpconfig = ldapConfig.ldapConfigCmd() - #Config the ldap server by assigning the ldapconfig dict variable values to ldapConfig object - lpconfig.hostname = ldapSrvD["ldapHostname"] - lpconfig.port = ldapSrvD["port"] - lpconfig.binddn = ldapSrvD["binddn"] - lpconfig.bindpass = ldapSrvD["bindpass"] - lpconfig.searchbase = ldapSrvD["searchbase"] - lpconfig.queryfilter = ldapSrvD["queryfilter"] + # Setup Global settings - #end of assigning the variables + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "ldap.basedn" + updateConfigurationCmd.value = ldapConfiguration['basedn'] + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) - #calling the ldapconfig Api - self.debug("calling ldapconfig API") + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "ldap.email.attribute" + updateConfigurationCmd.value = ldapConfiguration['emailAttribute'] + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) + + + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "ldap.realname.attribute" + updateConfigurationCmd.value = ldapConfiguration['realnameAttribute'] + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) + + + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "ldap.user.object" + updateConfigurationCmd.value = ldapConfiguration['userObject'] + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) + + + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "ldap.username.attribute" + updateConfigurationCmd.value = ldapConfiguration['usernameAttribute'] + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) + + self.debug("start addLdapConfiguration test") + + ldapServer = addLdapConfiguration.addLdapConfigurationCmd() + ldapServer.hostname = ldapConfiguration['hostname'] + ldapServer.port = ldapConfiguration['port'] + + self.debug("calling addLdapConfiguration API command") try: - lpconfig1 = self.apiclient.ldapConfig(lpconfig) - self.debug("ldapconfig API succesfful") + self.apiClient.addLdapConfiguration(ldapServer) + self.debug("addLdapConfiguration was successful") return 1 except Exception, e: - self.debug("ldapconfig API failed %s" %e) + self.debug("addLdapConfiguration failed %s" %e) return 0 - def chkLogin(self, username, password): + def _deleteLdapConfiguration(self,ldapConfiguration): + + """ + + :param ldapConfiguration + + """ + + ldapServer = deleteLdapConfiguration.deleteLdapConfigurationCmd() + ldapServer.hostname = ldapConfiguration["hostname"] + + try: + self.apiClient.deleteLdapConfiguration(ldapServer) + self.debug("deleteLdapConfiguration was successful") + return 1 + except Exception, e: + self.debug("deleteLdapConfiguration failed %s" %e) + return 0 + + def _checkLogin(self, username, password): """ :param username: :param password: """ - self.debug("login test") + self.debug("Attempting to login.") try: - login1 = login.loginCmd() - login1.username = username - login1.password = password - loginRes = self.apiclient.login(login1) + loginParams = login.loginCmd() + loginParams.username = username + loginParams.password = password + loginRes = self.apiClient.login(loginParams) self.debug("login response %s" % loginRes) if loginRes is None: self.debug("login not successful") + return 0 else: self.debug("login successful") return 1 diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 9c53a48ec52..2d96b1b2075 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -118,7 +118,7 @@ known_categories = { 'TrafficType': 'Usage', 'Product': 'Product', 'LB': 'Load Balancer', - 'ldap': 'LDAP', + 'Ldap': 'LDAP', 'Swift': 'Swift', 'S3' : 'S3', 'SecondaryStorage': 'Host', diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index c212223ca33..19b9b411e0c 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -5830,7 +5830,7 @@ label.error { .multi-wizard .buttons { width: 100%; position: absolute; - top: 519px; + bottom: 10px; left: 0px; } @@ -12267,3 +12267,98 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it color: #0000FF !important; } +.accounts-wizard table { + margin: 0; + width: 100%; + table-layout: fixed; +} +.accounts-wizard .ui-button { + display: inline-block !important; + float: none !important; +} +.accounts-wizard td:last-child { + border: none; +} +.accounts-wizard tbody tr:nth-child(even) { + background: #DFE1E3; +} +.accounts-wizard tbody tr:nth-child(odd) { + background: #F2F0F0; +} +.accounts-wizard .content { + display: inline-block; +} +.accounts-wizard .content:last-child { + margin-left: 14px; +} +.accounts-wizard .input-area { + width: 320px; + font-size: 13px; + color: #485867; + text-shadow: 0px 2px 1px #FFFFFF; +} +.ldap-account-choice { + border: none !important; + border-radius: 0 0 0 0 !important; +} +.manual-account-details .name { + margin-top: 2px; + width: 100px; + float: left; + padding-bottom:10px; +} +.manual-account-details { + height: auto !important; + overflow: visible !important; + overflow-x: visible !important; +} +.manual-account-details label.error { + display: block; + font-size: 10px; +} +.manual-account-details .value { + float: left; +} +.manual-account-details .form-item:after { + content:"."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} +.manual-account-details .form-item { + padding: 5px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.manual-account-details select, .manual-account-details input { + width: 150px; +} +.manual-account-details input { + background: #F6F6F6; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #AFAFAF; + -moz-box-shadow: inset 0px 1px #727272; + -webkit-box-shadow: inset 0px 1px #727272; + -o-box-shadow: inset 0px 1px #727272; + box-shadow: inset 0px 1px #727272; + -moz-box-shadow: inset 0px 1px 0px #727272; + -webkit-box-shadow: inset 0px 1px 0px #727272; + -o-box-shadow: inset 0px 1px 0px #727272; +} +.manual-account-details > *:nth-child(even) { + background: #DFE1E3; +} +.manual-account-details > *:nth-child(odd) { + background: #F2F0F0; +} +.manual-account-details .value { + display: inline-block; +} diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index 3dfdefe3218..15c199964b3 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -54,6 +54,7 @@ dictionary = { 'changed.item.properties': '', 'confirm.enable.s3': '', 'confirm.enable.swift': '', +'error.could.not.change.your.password.because.ldap.is.enabled': '', 'error.could.not.enable.zone': '', 'error.installWizard.message': '', 'error.invalid.username.password': '', @@ -526,7 +527,7 @@ dictionary = { 'label.edit.lb.rule': '', 'label.edit.network.details': '', 'label.edit.project.details': '', -'label.edit.tags': '', +'label.edit.tags': '', 'label.edit.traffic.type': '', 'label.edit.vpc': '', 'label.egress.rule': '', diff --git a/ui/index.jsp b/ui/index.jsp index 0ac48c93b84..086495a6c9b 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -238,10 +238,10 @@ under the License.
-
+

-

+

@@ -446,7 +446,7 @@ under the License.
- +
@@ -483,6 +483,37 @@ under the License.
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
@@ -519,7 +550,7 @@ under the License.
- +
@@ -547,7 +578,7 @@ under the License.
- +
@@ -766,7 +797,7 @@ under the License.
  • - +
    @@ -990,7 +1021,7 @@ under the License.
    -   +  
    @@ -1064,7 +1095,7 @@ under the License. " view-all-target="virtualRouters"> - +
    @@ -1239,7 +1270,7 @@ under the License.
    - +
    @@ -1632,7 +1663,7 @@ under the License. - + @@ -1656,7 +1687,7 @@ under the License. - + @@ -1664,7 +1695,7 @@ under the License. - + @@ -1685,8 +1716,8 @@ under the License. - - + + @@ -1701,6 +1732,8 @@ under the License. + + diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js index dcf28854e39..9006e74ff11 100644 --- a/ui/scripts/accounts.js +++ b/ui/scripts/accounts.js @@ -76,222 +76,20 @@ return 'label.add.account'; } }, - - createForm: { - title: 'label.add.account', - desc: 'label.add.account', - fields: { - username: { - label: 'label.username', - validation: { - required: true - }, - docID: 'helpAccountUsername' - }, - password: { - label: 'label.password', - validation: { - required: true - }, - isPassword: true, - id: 'password', - docID: 'helpAccountPassword' - }, - 'password-confirm': { - label: 'label.confirm.password', - validation: { - required: true, - equalTo: '#password' - }, - isPassword: true, - docID: 'helpAccountConfirmPassword' - }, - email: { - label: 'label.email', - validation: { - required: true, - email: true - }, - docID: 'helpAccountEmail' - }, - firstname: { - label: 'label.first.name', - validation: { - required: true - }, - docID: 'helpAccountFirstName' - }, - lastname: { - label: 'label.last.name', - validation: { - required: true - }, - docID: 'helpAccountLastName' - }, - domainid: { - label: 'label.domain', - docID: 'helpAccountDomain', - validation: { - required: true - }, - select: function(args) { - var data = {}; - - if (args.context.users) { // In accounts section - data.listAll = true; - } else if (args.context.domains) { // In domain section (use specific domain) - data.id = args.context.domains[0].id; - } - - $.ajax({ - url: createURL("listDomains"), - data: data, - dataType: "json", - async: false, - success: function(json) { - var items = []; - domainObjs = json.listdomainsresponse.domain; - $(domainObjs).each(function() { - items.push({ - id: this.id, - description: this.path - }); - - if (this.level == 0) - rootDomainId = this.id; - }); - args.response.success({ - data: items - }); - } - }); - } - }, - account: { - label: 'label.account', - docID: 'helpAccountAccount' - }, - accounttype: { - label: 'label.type', - docID: 'helpAccountType', - validation: { - required: true - }, - select: function(args) { - var items = []; - items.push({ - id: 0, - description: "User" - }); //regular-user - items.push({ - id: 1, - description: "Admin" - }); //root-admin - args.response.success({ - data: items - }); - } - }, - timezone: { - label: 'label.timezone', - docID: 'helpAccountTimezone', - select: function(args) { - var items = []; - items.push({ - id: "", - description: "" - }); - for (var p in timezoneMap) - items.push({ - id: p, - description: timezoneMap[p] - }); - args.response.success({ - data: items - }); - } - }, - networkdomain: { - label: 'label.network.domain', - docID: 'helpAccountNetworkDomain', - validation: { - required: false - } - } - } - }, - - action: function(args) { - var data = { - username: args.data.username, - }; - - var password = args.data.password; - if (md5Hashed) { - password = $.md5(password); - } - $.extend(data, { - password: password - }); - - $.extend(data, { - email: args.data.email, - firstname: args.data.firstname, - lastname: args.data.lastname, - domainid: args.data.domainid - }); - - var account = args.data.account; - if (account == null || account.length == 0) { - account = args.data.username; - } - $.extend(data, { - account: account - }); - - var accountType = args.data.accounttype; - if (args.data.accounttype == "1" && args.data.domainid != rootDomainId) { //if account type is admin, but domain is not Root domain - accountType = "2"; // Change accounttype from root-domain("1") to domain-admin("2") - } - $.extend(data, { - accounttype: accountType - }); - - if (args.data.timezone != null && args.data.timezone.length > 0) { - $.extend(data, { - timezone: args.data.timezone - }); - } - - if (args.data.networkdomain != null && args.data.networkdomain.length > 0) { - $.extend(data, { - networkdomain: args.data.networkdomain - }); - } - - $.ajax({ - url: createURL('createAccount'), - type: "POST", - data: data, - success: function(json) { - var item = json.createaccountresponse.account; - args.response.success({ - data: item - }); - }, - error: function(XMLHttpResponse) { - args.response.error(parseXMLHttpResponse(XMLHttpResponse)); - } - }); - }, - notification: { poll: function(args) { args.complete({ actionFilter: accountActionfilter }); } - } + }, + + action: { + custom: cloudStack.uiCustom.accountsWizard( + cloudStack.accountsWizard + ) + }, + } }, @@ -1254,47 +1052,56 @@ return 'label.action.change.password'; } }, - createForm: { - title: 'label.action.change.password', - fields: { - newPassword: { - label: 'label.new.password', - isPassword: true, - validation: { - required: true - }, - id: 'newPassword' - }, - 'password-confirm': { - label: 'label.confirm.password', - validation: { - required: true, - equalTo: '#newPassword' - }, - isPassword: true - } - } - }, - action: function(args) { - var password = args.data.newPassword; - if (md5Hashed) - password = $.md5(password); - var data = { - id: args.context.users[0].id, - password: password - }; + action: function(args) { + if (isLdapEnabled()) { + alert(dictionary["error.could.not.change.your.password.because.ldap.is.enabled"]); + args.response.error({}); + } else { + cloudStack.dialog.createForm({ + noDialog: false, + form: { + title: 'label.action.change.password', + fields: { + newPassword: { + label: 'label.new.password', + isPassword: true, + validation: { + required: true + }, + id: 'newPassword' + }, + 'password-confirm': { + label: 'label.confirm.password', + validation: { + required: true, + equalTo: '#newPassword' + }, + isPassword: true + } + } + } + }) + var password = args.data.newPassword; + if (md5Hashed) + password = $.md5(password); - $.ajax({ - url: createURL('updateUser'), - data: data, - type: "POST", - success: function(json) { - args.response.success({ - data: json.updateuserresponse.user - }); - } - }); + var data = { + id: args.context.users[0].id, + password: password + }; + $.ajax({ + url: createURL('updateUser'), + data: data, + type: "POST", + success: function(json) { + args.response.success({ + data: json.updateuserresponse.user + }); + } + }); + + } }, notification: { poll: function(args) { diff --git a/ui/scripts/accountsWizard.js b/ui/scripts/accountsWizard.js new file mode 100644 index 00000000000..70ef0820a32 --- /dev/null +++ b/ui/scripts/accountsWizard.js @@ -0,0 +1,288 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +(function(cloudStack, $) { + cloudStack.accountsWizard = { + + informationWithinLdap: { + username: { + label: 'label.username', + validation: { + required: true + }, + docID: 'helpAccountUsername' + }, + password: { + label: 'label.password', + validation: { + required: true + }, + isPassword: true, + id: 'password', + docID: 'helpAccountPassword' + }, + 'password-confirm': { + label: 'label.confirm.password', + validation: { + required: true, + equalTo: '#password' + }, + isPassword: true, + docID: 'helpAccountConfirmPassword' + }, + email: { + label: 'label.email', + validation: { + required: true, + email: true + }, + docID: 'helpAccountEmail' + }, + firstname: { + label: 'label.first.name', + validation: { + required: true + }, + docID: 'helpAccountFirstName' + }, + lastname: { + label: 'label.last.name', + validation: { + required: true + }, + docID: 'helpAccountLastName' + } + }, + + informationNotInLdap: { + domainid: { + label: 'label.domain', + docID: 'helpAccountDomain', + validation: { + required: true + }, + select: function(args) { + var data = {}; + + if (args.context.users) { // In accounts section + data.listAll = true; + } else if (args.context.domains) { // In domain section (use specific domain) + data.id = args.context.domains[0].id; + } + + $.ajax({ + url: createURL("listDomains"), + data: data, + dataType: "json", + async: false, + success: function(json) { + var items = []; + domainObjs = json.listdomainsresponse.domain; + $(domainObjs).each(function() { + items.push({ + id: this.id, + description: this.path + }); + + if (this.level === 0) + rootDomainId = this.id; + }); + args.response.success({ + data: items + }); + } + }); + } + }, + account: { + label: 'label.account', + docID: 'helpAccountAccount', + validation: { + required: false + } + }, + accounttype: { + label: 'label.type', + docID: 'helpAccountType', + validation: { + required: true + }, + select: function(args) { + var items = []; + items.push({ + id: 0, + description: "User" + }); //regular-user + items.push({ + id: 1, + description: "Admin" + }); //root-admin + args.response.success({ + data: items + }); + } + }, + timezone: { + label: 'label.timezone', + docID: 'helpAccountTimezone', + select: function(args) { + var items = []; + items.push({ + id: "", + description: "" + }); + for (var p in timezoneMap) + items.push({ + id: p, + description: timezoneMap[p] + }); + args.response.success({ + data: items + }); + } + }, + networkdomain: { + label: 'label.network.domain', + docID: 'helpAccountNetworkDomain', + validation: { + required: false + } + } + }, + + action: function(args) { + var array1 = []; + var ldapStatus = isLdapEnabled(); + console.log("creating user: " + args.username); + array1.push("&username=" + args.username); + + if (!ldapStatus) { + var password = args.data.password; + if (md5Hashed) { + password = $.md5(password); + } + array1.push("&email=" + args.data.email); + array1.push("&firstname=" + args.data.firstname); + array1.push("&lastname=" + args.data.lastname); + + var password = args.data.password; + if (md5Hashed) { + password = $.md5(password); + } + array1.push("&password=" + password); + } + + array1.push("&domainid=" + args.data.domainid); + + var account = args.data.account; + if (account === null || account.length === 0) { + account = args.username; + } + array1.push("&account=" + account); + + var accountType = args.data.accounttype; + if (args.data.accounttype == "1" && args.data.domainid != rootDomainId) { //if account type is admin, but domain is not Root domain + accountType = "2"; // Change accounttype from root-domain("1") to domain-admin("2") + } + array1.push("&accounttype=" + accountType); + + if (args.data.timezone !== null && args.data.timezone.length > 0) { + array1.push("&timezone=" + args.data.timezone); + } + + if (args.data.networkdomain !== null && args.data.networkdomain.length > 0) { + array1.push("&networkdomain=" + args.data.networkdomain); + } + + if (ldapStatus) { + console.log("doing an ldap add"); + $.ajax({ + url: createURL('ldapCreateAccount' + array1.join("")), + dataType: "json", + async: false, + success: function(json) { + var item = json.createaccountresponse.account; + args.response.success({ + data: item + }); + }, + error: function(XMLHttpResponse) { + args.response.error(parseXMLHttpResponse(XMLHttpResponse)); + } + }); + } else { + console.log("doing normal user add"); + $.ajax({ + url: createURL('createAccount' + array1.join("")), + dataType: "json", + async: false, + success: function(json) { + var item = json.createaccountresponse.account; + args.response.success({ + data: item + }); + }, + error: function(XMLHttpResponse) { + args.response.error(parseXMLHttpResponse(XMLHttpResponse)); + } + }); + } + } + /* + action: function(args) { + var array1 = []; + + var username = args.data.username; + + array1.push("&domainid=" + args.data.domainid); + + if (args.data.account != null && args.data.account.length != 0) { + array1.push("&account=" + args.data.account); + } + + if (args.data.accounttype == "1" && args.data.domainid != rootDomainId) { + args.data.accounttype = "2"; + } + array1.push("&accountType=" + args.data.accounttype); + + if (args.data.timezone != null && args.data.timezone.length != 0) { + array1.push("&timezone=" + args.data.timezone); + } + if (args.data.networkdomain != null && args.data.networkdomain != 0) { + array1.push("&networkDomain=" + args.data.networkdomain); + } + + for (var i = 0; i < username.length; i++) { + $.ajax({ + url: createURL("ldapCreateAccount&username=" + username[i] + array1.join("")), + dataType: "json", + async: false, + success: function(json) { + var item = json.createaccountresponse.account; + args.response.success({ + data: item + }); + }, + error: function(XMLHttpResponse) { + args.response.error(parseXMLHttpResponse(XMLHttpResponse)); + } + }); + } + } + */ + }; +}(cloudStack, jQuery)); diff --git a/ui/scripts/globalSettings.js b/ui/scripts/globalSettings.js index bee6ae3f489..d703e643bb9 100644 --- a/ui/scripts/globalSettings.js +++ b/ui/scripts/globalSettings.js @@ -99,7 +99,6 @@ } } }, - ldapConfiguration: { type: 'select', title: 'LDAP Configuration', @@ -110,29 +109,18 @@ hostname: { label: 'Hostname' }, - queryfilter: { - label: 'Query Filter' - }, - searchbase: { - label: 'Search Base' - }, port: { label: 'LDAP Port' }, - ssl: { - label: 'SSL' - - } - }, dataProvider: function(args) { var data = {}; listViewDataProvider(args, data); $.ajax({ - url: createURL('ldapConfig&listall=true'), //Need a list LDAP configuration API call which needs to be implemented + url: createURL('listLdapConfigurations'), data: data, success: function(json) { - var items = json.ldapconfigresponse.ldapconfig; + var items = json.ldapconfigurationresponse.LdapConfiguration; args.response.success({ data: items }); @@ -142,12 +130,9 @@ } }); }, - detailView: { name: 'label.details', actions: { - - // Remove LDAP remove: { label: 'Remove LDAP', messages: { @@ -159,192 +144,96 @@ } }, action: function(args) { - $.ajax({ - url: createURL("ldapRemove"), + url: createURL("deleteLdapConfiguration&hostname=" + args.context.ldapConfiguration[0].hostname), success: function(json) { - args.response.success(); - } - }); $(window).trigger('cloudStack.fullRefresh'); - } } }, - tabs: { - details: { title: 'LDAP Configuration Details', fields: [{ hostname: { label: 'Hostname' }, - description: { - label: 'label.description' - }, - ssl: { - label: 'SSL' + port: { + label: 'Port' } }], dataProvider: function(args) { + var items = []; + console.log(args); $.ajax({ - url: createURL("ldapConfig&listAll=true"), + url: createURL("listLdapConfigurations&hostname=" + args.context.ldapConfiguration[0].hostname), dataType: "json", async: true, success: function(json) { - var item = json.ldapconfigresponse.ldapconfig; + var item = json.ldapconfigurationresponse.LdapConfiguration; args.response.success({ - data: item + data: item[0] }); } }); } - } - } }, - actions: { add: { - label: 'Configure LDAP', - messages: { confirm: function(args) { return 'Do you really want to configure LDAP ? '; }, notification: function(args) { - return 'LDAP configured'; + console.log(args); + return 'Successfully added a new LDAP server'; } }, - createForm: { - title: 'Configure LDAP', fields: { - name: { - label: 'Bind DN', - validation: { - required: true - } - }, - password: { - label: 'Bind Password', - validation: { - required: true - }, - isPassword: true - }, hostname: { label: 'Hostname', validation: { required: true } }, - queryfilter: { - label: 'Query Filter', - validation: { - required: true - }, - docID: 'helpLdapQueryFilter' - }, - searchbase: { - label: 'SearchBase', - validation: { - required: true - } - }, - ssl: { - label: 'SSL', - isBoolean: true, - isChecked: false - - }, port: { label: 'Port', - defaultValue: '389' - }, - truststore: { - label: 'Trust Store', - isHidden: true, - dependsOn: 'ssl', - validation: { - required: true - } - }, - truststorepassword: { - label: 'Trust Store Password', - isHidden: true, - dependsOn: 'ssl', validation: { required: true } } - } - - }, - - action: function(args) { var array = []; - array.push("&binddn=" + todb(args.data.name)); - array.push("&bindpass=" + todb(args.data.password)); array.push("&hostname=" + todb(args.data.hostname)); - array.push("&searchbase=" + todb(args.data.searchbase)); - array.push("&queryfilter=" + todb(args.data.queryfilter)); - array.push("&port=" + todb(args.data.port)); - - if (args.$form.find('.form-item[rel=ssl]').find('input[type=checkbox]').is(':Checked') == true) { - - array.push("&ssl=true"); - if (args.data.truststore != "") - array.push("&truststore=" + todb(args.data.truststore)); - - if (args.data.truststorepassword != "") - array.push("&truststorepass=" + todb(args.data.truststorepassword)); - - } else - array.push("&ssl=false"); - + array.push("&port=" + todb(args.data.port));; $.ajax({ - url: createURL("ldapConfig" + array.join("")), + url: createURL("addLdapConfiguration" + array.join("")), dataType: "json", - type: "POST", + async: true, success: function(json) { - var items = json.ldapconfigresponse.ldapconfig; + var items = json.ldapconfigurationresponse.LdapAddConfiguration; args.response.success({ data: items }); - }, - error: function(json) { args.response.error(parseXMLHttpResponse(json)); - } - - }); - - } } - } - - - } - - - }, hypervisorCapabilities: { type: 'select', @@ -440,4 +329,4 @@ } } }; -})(cloudStack); +})(cloudStack); \ No newline at end of file diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js index 24aaafac307..c70ab0b1a0c 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -699,7 +699,23 @@ var addGuestNetworkDialog = { } -// Role Functions + function isLdapEnabled() { + var result; + $.ajax({ + url: createURL("listLdapConfigurations"), + dataType: "json", + async: false, + success: function(json) { + result = (json.ldapconfigurationresponse.count > 0); + }, + error: function(json) { + result = false; + } + }); + return result; + } + + // Role Functions function isAdmin() { return (g_role == 1); diff --git a/ui/scripts/ui-custom/accountsWizard.js b/ui/scripts/ui-custom/accountsWizard.js new file mode 100644 index 00000000000..358e29ca04b --- /dev/null +++ b/ui/scripts/ui-custom/accountsWizard.js @@ -0,0 +1,169 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +(function($, cloudStack) { + cloudStack.uiCustom.accountsWizard = function(args) { + return function(listViewArgs) { + var context = listViewArgs.context; + var ldapStatus = isLdapEnabled(); + var accountsWizard = function(data) { + var $wizard = $('#template').find('div.accounts-wizard').clone(); + var $form = $wizard.find('form'); + + var close = function() { + $wizard.dialog('destroy'); + $('div.overlay').fadeOut(function() { + $('div.overlay').remove(); + }); + }; + + var completeAction = function() { + var data = cloudStack.serializeForm($form); + var username = data.username; + var bulkAdd = (username instanceof Array); + if (bulkAdd) { + console.log("doing bulk add"); + for (var i = 0; i < username.length; i++) { + console.log("creating user " + username[i]); + args.action({ + context: context, + data: data, + username: username[i], + response: { + error: function(message) { + if (message) { + cloudStack.dialog.notice({ + message: message + }); + } + } + } + }); + } + } else { + args.action({ + context: context, + data: data, + username: username, + response: { + error: function(message) { + if (message) { + cloudStack.dialog.notice({ + message: message + }); + } + } + } + }); + } + }; + + $wizard.click(function(event) { + var $target = $(event.target); + if ($target.closest('button.next').size()) { + $form.validate(); + if ($form.valid()) { + completeAction(); + $(window).trigger('cloudStack.fullRefresh'); + close(); + return true; + } + } + + if ($target.closest('button.cancel').size()) { + close(); + return false; + } + }); + + if (ldapStatus) { + var $table = $wizard.find('.ldap-account-choice tbody'); + $.ajax({ + url: createURL("listLdapUsers&listtype=new"), + dataType: "json", + async: false, + success: function(json) { + if (json.ldapuserresponse.count > 0) { + $(json.ldapuserresponse.LdapUser).each(function() { + var result = $(""); + result.append(""); + result.append("" + this.firstname + " " + this.lastname + ""); + result.append("" + this.username + ""); + result.append("" + this.email + ""); + $table.append(result); + }); + } else { + var result = $(""); + result.append("No data to show"); + $table.append(result); + } + } + }); + } else { + var informationWithinLdap = cloudStack.dialog.createForm({ + context: context, + noDialog: true, + form: { + title: '', + fields: args.informationWithinLdap + } + }); + + //console.log(informationWithinLdap.$formContainer); + var informationWithinLdapForm = informationWithinLdap.$formContainer.find('form .form-item'); + informationWithinLdapForm.find('.value #label_username').addClass('required'); + informationWithinLdapForm.find('.value #password').addClass('required'); + informationWithinLdapForm.find('.value #label_confirm_password').addClass('required'); + informationWithinLdapForm.find('.value #label_confirm_password').attr('equalTo', '#password'); + informationWithinLdapForm.find('.value #label_email').addClass('required'); + informationWithinLdapForm.find('.value #label_first_name').addClass('required'); + informationWithinLdapForm.find('.value #label_last_name').addClass('required'); + $wizard.find('.manual-account-details').append(informationWithinLdapForm).children().css('background', 'none'); + $wizard.find('.ldap-account-choice').css('display', 'none'); + $wizard.removeClass('multi-wizard'); + } + + var informationNotInLdap = cloudStack.dialog.createForm({ + context: context, + noDialog: true, + form: { + title: '', + fields: args.informationNotInLdap + } + }); + + var informationNotInLdapForm = informationNotInLdap.$formContainer.find('form .form-item'); + informationNotInLdapForm.find('.value #label_domain').addClass('required'); + informationNotInLdapForm.find('.value #label_type').addClass('required'); + if (!ldapStatus) { + informationNotInLdapForm.css('background', 'none'); + } + $wizard.find('.manual-account-details').append(informationNotInLdapForm); + + return $wizard.dialog({ + title: _l('label.add.account'), + width: ldapStatus ? 800 : 330, + height: ldapStatus ? 500 : 500, + closeOnEscape: false, + zIndex: 5000 + }).closest('.ui-dialog').overlay(); + }; + + accountsWizard(args); + }; + }; +})(jQuery, cloudStack);