From 5e6dd45e30cc0d02ca6ce6cf53ce15d5d90343aa Mon Sep 17 00:00:00 2001 From: dahn Date: Mon, 5 Mar 2018 11:57:38 +0100 Subject: [PATCH] Fr21 ldap account binding and prerequisites (#57) * internal service call for moveUser * expose moveUser as API * move uuid to external entity * fr21: Account ldap binding * downgrade reflections * extra tracing * details improvement * don't refuse non exixting user --- api/pom.xml | 5 + api/src/com/cloud/event/EventTypes.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 2 + .../command/admin/config/ListCfgsByCmd.java | 14 + .../command/admin/config/UpdateCfgCmd.java | 14 + .../api/command/admin/user/MoveUserCmd.java | 126 + .../cloudstack/region/RegionService.java | 10 +- ...spring-engine-schema-core-daos-context.xml | 1 + .../src/com/cloud/domain/DomainDetailVO.java | 76 + .../cloud/domain/dao/DomainDetailsDao.java | 34 + .../domain/dao/DomainDetailsDaoImpl.java | 104 + engine/schema/src/com/cloud/user/UserVO.java | 20 +- .../framework/config/ConfigKey.java | 5 +- .../config/impl/ConfigDepotImpl.java | 1 + .../IntegrationTestConfiguration.java | 7 +- .../management/MockAccountManager.java | 11 + plugins/user-authenticators/ldap/pom.xml | 8 +- .../cloudstack/ldap/spring-ldap-context.xml | 3 +- .../cloudstack/api/command/LDAPConfigCmd.java | 8 +- .../cloudstack/api/command/LDAPRemoveCmd.java | 4 +- .../api/command/LdapAddConfigurationCmd.java | 15 +- .../api/command/LdapCreateAccountCmd.java | 2 +- .../command/LdapDeleteConfigurationCmd.java | 25 +- .../api/command/LdapImportUsersCmd.java | 4 +- .../api/command/LdapListConfigurationCmd.java | 17 +- .../api/command/LdapListUsersCmd.java | 2 +- .../api/command/LinkAccountToLdapCmd.java | 142 ++ .../api/command/LinkDomainToLdapCmd.java | 141 ++ .../response/LdapConfigurationResponse.java | 35 +- .../response/LinkAccountToLdapResponse.java | 85 + .../response/LinkDomainToLdapResponse.java | 84 + .../ldap/ADLdapUserManagerImpl.java | 104 + .../cloudstack/ldap/LdapAuthenticator.java | 239 +- .../cloudstack/ldap/LdapConfiguration.java | 306 ++- .../cloudstack/ldap/LdapConfigurationVO.java | 10 +- .../cloudstack/ldap/LdapContextFactory.java | 38 +- .../apache/cloudstack/ldap/LdapManager.java | 42 +- .../cloudstack/ldap/LdapManagerImpl.java | 239 +- .../cloudstack/ldap/LdapTrustMapVO.java | 127 + .../org/apache/cloudstack/ldap/LdapUser.java | 18 +- .../cloudstack/ldap/LdapUserManager.java | 205 +- .../ldap/LdapUserManagerFactory.java | 64 + .../org/apache/cloudstack/ldap/LdapUtils.java | 15 + .../ldap/OpenLdapUserManagerImpl.java | 311 +++ .../ldap/dao/LdapConfigurationDao.java | 13 +- .../ldap/dao/LdapConfigurationDaoImpl.java | 43 +- .../cloudstack/ldap/dao/LdapTrustMapDao.java | 32 + .../ldap/dao/LdapTrustMapDaoImpl.java | 80 + .../ldap/ADLdapUserManagerImplSpec.groovy | 85 + .../ldap/LdapAuthenticatorSpec.groovy | 184 +- .../ldap/LdapConfigurationDaoImplSpec.groovy | 4 +- .../ldap/LdapConfigurationSpec.groovy | 137 +- .../ldap/LdapContextFactorySpec.groovy | 11 +- .../ldap/LdapCreateAccountCmdSpec.groovy | 119 +- .../LdapDeleteConfigurationCmdSpec.groovy | 4 +- .../ldap/LdapImportUsersCmdSpec.groovy | 42 +- .../ldap/LdapListUsersCmdSpec.groovy | 10 +- .../ldap/LdapManagerImplSpec.groovy | 217 +- .../ldap/LdapSearchUserCmdSpec.groovy | 2 +- .../ldap/LdapUserManagerFactorySpec.groovy | 57 + .../cloudstack/ldap/LdapUserSpec.groovy | 22 +- .../ldap/LinkDomainToLdapCmdSpec.groovy | 232 ++ .../ldap/OpenLdapUserManagerSpec.groovy | 335 +++ .../api/command/LdapConfigurationChanger.java | 56 + .../api/command/LdapCreateAccountCmdTest.java | 72 + .../api/command/LdapImportUsersCmdTest.java | 85 + .../api/command/LinkAccountToLdapCmdTest.java | 97 + .../api/command/LinkDomainToLdapCmdTest.java | 98 + .../ldap/LdapConfigurationTest.java | 141 ++ pom.xml | 7 +- .../src/com/cloud/configuration/Config.java | 59 +- .../ConfigurationManagerImpl.java | 28 +- .../cloud/server/ManagementServerImpl.java | 24 +- server/src/com/cloud/user/AccountManager.java | 12 +- .../com/cloud/user/AccountManagerImpl.java | 153 +- .../cloudstack/region/RegionManager.java | 14 +- .../cloudstack/region/RegionManagerImpl.java | 9 + .../cloudstack/region/RegionServiceImpl.java | 9 + .../cloud/user/MockAccountManagerImpl.java | 11 + .../ChildTestConfiguration.java | 6 + setup/db/db/schema-452to453-cleanup.sql | 1 + setup/db/db/schema-452to453.sql | 24 + test/integration/smoke/test_accounts.py | 2225 +++++++++++++++++ tools/marvin/marvin/config/test_data.py | 9 + tools/marvin/marvin/lib/base.py | 13 + ui/scripts/domains.js | 90 +- 86 files changed, 6784 insertions(+), 812 deletions(-) create mode 100644 api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java create mode 100644 engine/schema/src/com/cloud/domain/DomainDetailVO.java create mode 100644 engine/schema/src/com/cloud/domain/dao/DomainDetailsDao.java create mode 100644 engine/schema/src/com/cloud/domain/dao/DomainDetailsDaoImpl.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkAccountToLdapCmd.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkDomainToLdapCmd.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkAccountToLdapResponse.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkDomainToLdapResponse.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapTrustMapVO.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManagerFactory.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java create mode 100644 plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java create mode 100644 plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/ADLdapUserManagerImplSpec.groovy create mode 100644 plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerFactorySpec.groovy create mode 100644 plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LinkDomainToLdapCmdSpec.groovy create mode 100644 plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/OpenLdapUserManagerSpec.groovy create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapConfigurationChanger.java create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java create mode 100644 plugins/user-authenticators/ldap/test/org/apache/cloudstack/ldap/LdapConfigurationTest.java create mode 100644 test/integration/smoke/test_accounts.py diff --git a/api/pom.xml b/api/pom.xml index dfb35728d5f..013fdfafbd0 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -56,6 +56,11 @@ cloud-framework-ca ${project.version} + + org.apache.commons + commons-lang3 + ${cs.commons-lang3.version} + diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 4ecdbf42861..d9127303b67 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -197,6 +197,7 @@ public class EventTypes { public static final String EVENT_USER_CREATE = "USER.CREATE"; public static final String EVENT_USER_DELETE = "USER.DELETE"; public static final String EVENT_USER_DISABLE = "USER.DISABLE"; + public static final String EVENT_USER_MOVE = "USER.MOVE"; public static final String EVENT_USER_UPDATE = "USER.UPDATE"; public static final String EVENT_USER_ENABLE = "USER.ENABLE"; public static final String EVENT_USER_LOCK = "USER.LOCK"; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 4f0e59b2eae..6e822f75178 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -646,6 +646,8 @@ public class ApiConstants { public static final String UTILIZATION = "utilization"; public static final String HAS_ANNOTATION = "hasannotation"; public static final String LAST_ANNOTATED = "lastannotated"; + public static final String ADMIN = "admin"; + public static final String LDAP_DOMAIN = "ldapdomain"; public enum HostDetails { all, capacity, events, stats, min; diff --git a/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java b/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java index a34bc3eb622..ccfc432870b 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.admin.config; import java.util.ArrayList; import java.util.List; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -76,6 +77,12 @@ public class ListCfgsByCmd extends BaseListCmd { description = "the ID of the Account to update the parameter value for corresponding account") private Long accountId; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the ID of the Domain to update the parameter value for corresponding domain") + private Long domainId; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -104,6 +111,10 @@ public class ListCfgsByCmd extends BaseListCmd { return accountId; } + public Long getDomainId() { + return domainId; + } + @Override public Long getPageSizeVal() { Long defaultPageSize = 500L; @@ -147,6 +158,9 @@ public class ListCfgsByCmd extends BaseListCmd { if (getAccountId() != null) { cfgResponse.setScope("account"); } + if (getDomainId() != null) { + cfgResponse.setScope("domain"); + } configResponses.add(cfgResponse); } diff --git a/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java b/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java index 45f790fb70b..b4e35d0f59d 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.config; import com.google.common.base.Strings; import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -75,6 +76,12 @@ public class UpdateCfgCmd extends BaseCmd { description = "the ID of the Account to update the parameter value for corresponding account") private Long accountId; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the ID of the Domain to update the parameter value for corresponding domain") + private Long domainId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -107,6 +114,10 @@ public class UpdateCfgCmd extends BaseCmd { return accountId; } + public Long getDomainId() { + return domainId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -145,6 +156,9 @@ public class UpdateCfgCmd extends BaseCmd { if (getAccountId() != null) { response.setScope("account"); } + if (getDomainId() != null) { + response.setScope("domain"); + } response.setValue(value); this.setResponseObject(response); } else { diff --git a/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java b/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java new file mode 100644 index 00000000000..b32aa2f1326 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java @@ -0,0 +1,126 @@ +// 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.user; + +import com.cloud.user.Account; +import com.cloud.user.User; +import com.google.common.base.Preconditions; +import org.apache.cloudstack.acl.RoleType; +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.SuccessResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.region.RegionService; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +@APICommand(name = "moveUser", + description = "Moves a user to another account", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.11", + authorized = {RoleType.Admin}) +public class MoveUserCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(UpdateUserCmd.class.getName()); + + public static final String APINAME = "moveUser"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = UserResponse.class, + required = true, + description = "id of the user to be deleted") + private Long id; + + @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_ID, + type = CommandType.UUID, + entityType = AccountResponse.class, + description = "Creates the user under the specified domain. Has to be accompanied with the account parameter") + private Long accountId; + + @Inject + RegionService _regionService; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getAccountName() { + return accountName; + } + + public Long getAccountId() { + return accountId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + User user = _entityMgr.findById(User.class, getId()); + if (user != null) { + return user.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute() { + Preconditions.checkNotNull(getId(),"I have to have an user to move!"); + Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!"); + + CallContext.current().setEventDetails("UserId: " + getId()); + boolean result = + _regionService.moveUser(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the user to a new account"); + } + } + +} diff --git a/api/src/org/apache/cloudstack/region/RegionService.java b/api/src/org/apache/cloudstack/region/RegionService.java index afefcc7672e..bee66910b4d 100644 --- a/api/src/org/apache/cloudstack/region/RegionService.java +++ b/api/src/org/apache/cloudstack/region/RegionService.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; import org.apache.cloudstack.api.command.admin.user.DisableUserCmd; import org.apache.cloudstack.api.command.admin.user.EnableUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import org.apache.cloudstack.api.command.user.region.ListRegionsCmd; @@ -110,10 +111,17 @@ public interface RegionService { */ boolean deleteUser(DeleteUserCmd deleteUserCmd); + /** + * Deletes user by Id + * @param moveUserCmd + * @return true if delete was successful, false otherwise + */ + boolean moveUser(MoveUserCmd moveUserCmd); + /** * update an existing domain * - * @param cmd + * @param updateDomainCmd * - the command containing domainId and new domainName * @return Domain object if the command succeeded */ diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 066d93ea03f..5f0802e4d73 100644 --- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -146,6 +146,7 @@ + diff --git a/engine/schema/src/com/cloud/domain/DomainDetailVO.java b/engine/schema/src/com/cloud/domain/DomainDetailVO.java new file mode 100644 index 00000000000..61eb6cfd28e --- /dev/null +++ b/engine/schema/src/com/cloud/domain/DomainDetailVO.java @@ -0,0 +1,76 @@ +// 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.domain; + +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 com.cloud.utils.db.Encrypt; +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "domain_details") +public class DomainDetailVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "domain_id") + private long domainId; + + @Column(name = "name") + private String name; + + @Encrypt + @Column(name = "value") + private String value; + + protected DomainDetailVO() { + } + + public DomainDetailVO(long domainId, String name, String value) { + this.domainId = domainId; + this.name = name; + this.value = value; + } + + public long getDomainId() { + return domainId; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public long getId() { + return id; + } +} diff --git a/engine/schema/src/com/cloud/domain/dao/DomainDetailsDao.java b/engine/schema/src/com/cloud/domain/dao/DomainDetailsDao.java new file mode 100644 index 00000000000..51362cf885e --- /dev/null +++ b/engine/schema/src/com/cloud/domain/dao/DomainDetailsDao.java @@ -0,0 +1,34 @@ +// 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.domain.dao; + +import java.util.Map; + +import com.cloud.domain.DomainDetailVO; +import com.cloud.utils.db.GenericDao; + +public interface DomainDetailsDao extends GenericDao { + Map findDetails(long domainId); + + void persist(long domainId, Map details); + + DomainDetailVO findDetail(long domainId, String name); + + void deleteDetails(long domainId); + + void update(long domainId, Map details); +} diff --git a/engine/schema/src/com/cloud/domain/dao/DomainDetailsDaoImpl.java b/engine/schema/src/com/cloud/domain/dao/DomainDetailsDaoImpl.java new file mode 100644 index 00000000000..ad7f7040207 --- /dev/null +++ b/engine/schema/src/com/cloud/domain/dao/DomainDetailsDaoImpl.java @@ -0,0 +1,104 @@ +// 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.domain.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.cloud.domain.DomainDetailVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.TransactionLegacy; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ConfigKey.Scope; +import org.apache.cloudstack.framework.config.ScopedConfigStorage; + +public class DomainDetailsDaoImpl extends GenericDaoBase implements DomainDetailsDao, ScopedConfigStorage { + protected final SearchBuilder domainSearch; + + protected DomainDetailsDaoImpl() { + domainSearch = createSearchBuilder(); + domainSearch.and("domainId", domainSearch.entity().getDomainId(), Op.EQ); + domainSearch.done(); + } + + @Override + public Map findDetails(long domainId) { + QueryBuilder sc = QueryBuilder.create(DomainDetailVO.class); + sc.and(sc.entity().getDomainId(), Op.EQ, domainId); + List results = sc.list(); + Map details = new HashMap(results.size()); + for (DomainDetailVO r : results) { + details.put(r.getName(), r.getValue()); + } + return details; + } + + @Override + public void persist(long domainId, Map details) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + SearchCriteria sc = domainSearch.create(); + sc.setParameters("domainId", domainId); + expunge(sc); + for (Map.Entry detail : details.entrySet()) { + DomainDetailVO vo = new DomainDetailVO(domainId, detail.getKey(), detail.getValue()); + persist(vo); + } + txn.commit(); + } + + @Override + public DomainDetailVO findDetail(long domainId, String name) { + QueryBuilder sc = QueryBuilder.create(DomainDetailVO.class); + sc.and(sc.entity().getDomainId(), Op.EQ, domainId); + sc.and(sc.entity().getName(), Op.EQ, name); + return sc.find(); + } + + @Override + public void deleteDetails(long domainId) { + SearchCriteria sc = domainSearch.create(); + sc.setParameters("domainId", domainId); + List results = search(sc, null); + for (DomainDetailVO result : results) { + remove(result.getId()); + } + } + + @Override + public void update(long domainId, Map details) { + Map oldDetails = findDetails(domainId); + oldDetails.putAll(details); + persist(domainId, oldDetails); + } + + @Override + public Scope getScope() { + return Scope.Domain; + } + + @Override + public String getConfigValue(long id, ConfigKey key) { + DomainDetailVO vo = findDetail(id, key.key()); + return vo == null ? null : vo.getValue(); + } +} diff --git a/engine/schema/src/com/cloud/user/UserVO.java b/engine/schema/src/com/cloud/user/UserVO.java index da7811ecc5b..9b744617fcd 100644 --- a/engine/schema/src/com/cloud/user/UserVO.java +++ b/engine/schema/src/com/cloud/user/UserVO.java @@ -126,7 +126,25 @@ public class UserVO implements User, Identity, InternalIdentity { this.source = source; } - @Override + public UserVO(UserVO user) { + this.setAccountId(user.getAccountId()); + this.setUsername(user.getUsername()); + this.setPassword(user.getPassword()); + this.setFirstname(user.getFirstname()); + this.setLastname(user.getLastname()); + this.setEmail(user.getEmail()); + this.setTimezone(user.getTimezone()); + this.setUuid(user.getUuid()); + this.setSource(user.getSource()); + this.setApiKey(user.getApiKey()); + this.setSecretKey(user.getSecretKey()); + this.setExternalEntity(user.getExternalEntity()); + this.setRegistered(user.isRegistered()); + this.setRegistrationToken(user.getRegistrationToken()); + this.setState(user.getState()); + } + + @Override public long getId() { return id; } diff --git a/framework/config/src/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/org/apache/cloudstack/framework/config/ConfigKey.java index d39bb5d3562..2f63a2837fc 100644 --- a/framework/config/src/org/apache/cloudstack/framework/config/ConfigKey.java +++ b/framework/config/src/org/apache/cloudstack/framework/config/ConfigKey.java @@ -31,7 +31,7 @@ import com.cloud.utils.exception.CloudRuntimeException; public class ConfigKey { public static enum Scope { - Global, Zone, Cluster, StoragePool, Account, ManagementServer + Global, Zone, Cluster, StoragePool, Account, ManagementServer, Domain } private final String _category; @@ -133,7 +133,8 @@ public class ConfigKey { public T value() { if (_value == null || isDynamic()) { ConfigurationVO vo = s_depot != null ? s_depot.global().findById(key()) : null; - _value = valueOf((vo != null && vo.getValue() != null) ? vo.getValue() : defaultValue()); + final String value = (vo != null && vo.getValue() != null) ? vo.getValue() : defaultValue(); + _value = ((value == null) ? (T)defaultValue() : valueOf(value)); } return _value; diff --git a/framework/config/src/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java b/framework/config/src/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java index 4631bb9d0e8..07514f61f4a 100644 --- a/framework/config/src/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java +++ b/framework/config/src/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java @@ -84,6 +84,7 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { _scopeLevelConfigsMap.put(ConfigKey.Scope.Cluster, new HashSet>()); _scopeLevelConfigsMap.put(ConfigKey.Scope.StoragePool, new HashSet>()); _scopeLevelConfigsMap.put(ConfigKey.Scope.Account, new HashSet>()); + _scopeLevelConfigsMap.put(ConfigKey.Scope.Domain, new HashSet>()); } @Override diff --git a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java index 954a37e1fe6..3e6677d0b0a 100644 --- a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java +++ b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java @@ -140,6 +140,7 @@ import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.dao.PlannerHostReservationDaoImpl; import com.cloud.domain.dao.DomainDaoImpl; +import com.cloud.domain.dao.DomainDetailsDaoImpl; import com.cloud.event.dao.EventDaoImpl; import com.cloud.event.dao.EventJoinDaoImpl; import com.cloud.event.dao.UsageEventDaoImpl; @@ -148,8 +149,8 @@ import com.cloud.host.dao.HostDaoImpl; import com.cloud.host.dao.HostDetailsDaoImpl; import com.cloud.host.dao.HostTagsDaoImpl; import com.cloud.hypervisor.HypervisorGuruManagerImpl; -import com.cloud.hypervisor.dao.HypervisorCapabilitiesDaoImpl; import com.cloud.hypervisor.XenServerGuru; +import com.cloud.hypervisor.dao.HypervisorCapabilitiesDaoImpl; import com.cloud.network.ExternalDeviceUsageManager; import com.cloud.network.IpAddress; import com.cloud.network.IpAddressManagerImpl; @@ -169,8 +170,8 @@ import com.cloud.network.as.dao.CounterDaoImpl; import com.cloud.network.dao.AccountGuestVlanMapDaoImpl; import com.cloud.network.dao.FirewallRulesCidrsDaoImpl; import com.cloud.network.dao.FirewallRulesDaoImpl; -import com.cloud.network.dao.IPAddressDaoImpl; import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressDaoImpl; import com.cloud.network.dao.LBHealthCheckPolicyDaoImpl; import com.cloud.network.dao.LBStickinessPolicyDaoImpl; import com.cloud.network.dao.LoadBalancerDaoImpl; @@ -308,7 +309,7 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDaoImpl; ConditionDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationManagerImpl.class, ConfigurationServerImpl.class, ConsoleProxyDaoImpl.class, ContrailElementImpl.class, ContrailGuru.class, ContrailManagerImpl.class, CounterDaoImpl.class, DataCenterDaoImpl.class, DataCenterDetailsDaoImpl.class, DataCenterIpAddressDaoImpl.class, DataCenterJoinDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, DataCenterVnetDaoImpl.class, DcDetailsDaoImpl.class, DedicatedResourceDaoImpl.class, - DiskOfferingDaoImpl.class, DiskOfferingJoinDaoImpl.class, DomainDaoImpl.class, DomainManagerImpl.class, DomainRouterDaoImpl.class, DomainRouterJoinDaoImpl.class, + DiskOfferingDaoImpl.class, DiskOfferingJoinDaoImpl.class, DomainDaoImpl.class, DomainDetailsDaoImpl.class, DomainManagerImpl.class, DomainRouterDaoImpl.class, DomainRouterJoinDaoImpl.class, EventDaoImpl.class, EventJoinDaoImpl.class, EventUtils.class, ExtensionRegistry.class, FirewallManagerImpl.class, FirewallRulesCidrsDaoImpl.class, FirewallRulesDaoImpl.class, GuestOSCategoryDaoImpl.class, GuestOSDaoImpl.class, HostDaoImpl.class, HostDetailsDaoImpl.class, HostJoinDaoImpl.class, HostPodDaoImpl.class, HostTagsDaoImpl.class, HostTransferMapDaoImpl.class, HypervisorCapabilitiesDaoImpl.class, HypervisorGuruManagerImpl.class, diff --git a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index f9f0d2e2c9f..857e6266c8b 100644 --- a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -25,6 +25,7 @@ import javax.naming.ConfigurationException; import org.apache.log4j.Logger; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -311,6 +312,16 @@ public class MockAccountManager extends ManagerBase implements AccountManager { return false; } + @Override + public boolean moveUser(MoveUserCmd moveUserCmd) { + return false; + } + + @Override + public boolean moveUser(long id, Long domainId, long accountId) { + return false; + } + @Override public boolean deleteUserAccount(long arg0) { // TODO Auto-generated method stub diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index ec2813e00da..41d5eebb711 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -37,9 +37,9 @@ - test/groovy + test - **/*.groovy + groovy/**/*.groovy @@ -70,7 +70,8 @@ maven-surefire-plugin - **/*Spec* + **/*Spec.groovy + **/*Test.java @@ -90,6 +91,7 @@ + test diff --git a/plugins/user-authenticators/ldap/resources/META-INF/cloudstack/ldap/spring-ldap-context.xml b/plugins/user-authenticators/ldap/resources/META-INF/cloudstack/ldap/spring-ldap-context.xml index 34a2befe971..07d6b381328 100644 --- a/plugins/user-authenticators/ldap/resources/META-INF/cloudstack/ldap/spring-ldap-context.xml +++ b/plugins/user-authenticators/ldap/resources/META-INF/cloudstack/ldap/spring-ldap-context.xml @@ -30,10 +30,11 @@ - + + diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPConfigCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPConfigCmd.java index a138e7ddd4a..cfef21e2aff 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPConfigCmd.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPConfigCmd.java @@ -49,7 +49,7 @@ import com.cloud.utils.Pair; * @deprecated as of 4.3 use the new api {@link LdapAddConfigurationCmd} */ @Deprecated -@APICommand(name = "ldapConfig", description = "Configure the LDAP context for this site.", responseObject = LDAPConfigResponse.class, since = "3.0.0", +@APICommand(name = "ldapConfig", description = "(Deprecated, use addLdapConfiguration) Configure the LDAP context for this site.", responseObject = LDAPConfigResponse.class, since = "3.0.0", requestHasSensitiveInfo = true, responseHasSensitiveInfo = false) public class LDAPConfigCmd extends BaseCmd { @@ -190,8 +190,8 @@ public class LDAPConfigCmd extends BaseCmd { if (result.second() > 0) { boolean useSSlConfig = _ldapConfiguration.getSSLStatus(); - String searchBaseConfig = _ldapConfiguration.getBaseDn(); - String bindDnConfig = _ldapConfiguration.getBindPrincipal(); + String searchBaseConfig = _ldapConfiguration.getBaseDn(null); + String bindDnConfig = _ldapConfiguration.getBindPrincipal(null); for (LdapConfigurationVO ldapConfigurationVO : result.first()) { responses.add(createLDAPConfigResponse(ldapConfigurationVO.getHostname(), ldapConfigurationVO.getPort(), useSSlConfig, null, searchBaseConfig, bindDnConfig)); @@ -226,7 +226,7 @@ public class LDAPConfigCmd extends BaseCmd { } private boolean updateLDAP() { - _ldapManager.addConfiguration(hostname, port); + _ldapManager.addConfiguration(hostname, port, null); /** * There is no query filter now. It is derived from ldap.user.object and ldap.search.group.principle diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPRemoveCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPRemoveCmd.java index eb3729d9d9e..0a4dc20ee0b 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPRemoveCmd.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LDAPRemoveCmd.java @@ -35,7 +35,7 @@ import com.cloud.utils.Pair; * @deprecated as of 4.3 use the new api {@link LdapDeleteConfigurationCmd} */ @Deprecated -@APICommand(name = "ldapRemove", description = "Remove the LDAP context for this site.", responseObject = LDAPConfigResponse.class, since = "3.0.1", +@APICommand(name = "ldapRemove", description = "(Deprecated , use deleteLdapConfiguration) Remove the LDAP context for this site.", responseObject = LDAPConfigResponse.class, since = "3.0.1", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class LDAPRemoveCmd extends BaseCmd { public static final Logger s_logger = Logger.getLogger(LDAPRemoveCmd.class.getName()); @@ -60,7 +60,7 @@ public class LDAPRemoveCmd extends BaseCmd { LdapListConfigurationCmd listConfigurationCmd = new LdapListConfigurationCmd(_ldapManager); Pair, Integer> result = _ldapManager.listConfigurations(listConfigurationCmd); for (LdapConfigurationVO config : result.first()) { - _ldapManager.deleteConfiguration(config.getHostname()); + _ldapManager.deleteConfiguration(config.getHostname(), 0, null); } return true; } 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 index 555d1a987fd..7c592888364 100644 --- 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 @@ -18,6 +18,8 @@ package org.apache.cloudstack.api.command; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -40,12 +42,15 @@ public class LdapAddConfigurationCmd extends BaseCmd { @Inject private LdapManager _ldapManager; - @Parameter(name = "hostname", type = CommandType.STRING, required = true, description = "Hostname") + @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, required = true, description = "Hostname") private String hostname; - @Parameter(name = "port", type = CommandType.INTEGER, required = true, description = "Port") + @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = true, description = "Port") private int port; + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain") + private Long domainId; + public LdapAddConfigurationCmd() { super(); } @@ -58,7 +63,7 @@ public class LdapAddConfigurationCmd extends BaseCmd { @Override public void execute() throws ServerApiException { try { - final LdapConfigurationResponse response = _ldapManager.addConfiguration(hostname, port); + final LdapConfigurationResponse response = _ldapManager.addConfiguration(hostname, port, domainId); response.setObjectName("LdapAddConfiguration"); response.setResponseName(getCommandName()); setResponseObject(response); @@ -86,6 +91,10 @@ public class LdapAddConfigurationCmd extends BaseCmd { return port; } + public Long getDomainId() { + return domainId; + } + public void setHostname(final String hostname) { this.hostname = hostname; } 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 index 4da04166b1b..272a8a22f09 100644 --- 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 @@ -138,7 +138,7 @@ public class LdapCreateAccountCmd extends BaseCmd { Long finalDomainId = getDomainId(); callContext.setEventDetails("Account Name: " + finalAccountName + ", Domain Id:" + finalDomainId); try { - final LdapUser user = _ldapManager.getUser(username); + final LdapUser user = _ldapManager.getUser(username, domainId); validateUser(user); final UserAccount userAccount = createCloudstackUserAccount(user, finalAccountName, finalDomainId); if (userAccount != null) { 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 index 30b37d8b88d..3ffebecfb95 100644 --- 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 @@ -18,6 +18,8 @@ package org.apache.cloudstack.api.command; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -40,9 +42,16 @@ public class LdapDeleteConfigurationCmd extends BaseCmd { @Inject private LdapManager _ldapManager; - @Parameter(name = "hostname", type = CommandType.STRING, required = true, description = "Hostname") + + @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, required = true, description = "Hostname") private String hostname; + @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "port") + private int port; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain") + private Long domainId; + public LdapDeleteConfigurationCmd() { super(); } @@ -52,10 +61,22 @@ public class LdapDeleteConfigurationCmd extends BaseCmd { _ldapManager = ldapManager; } + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public Long getDomainId() { + return domainId; + } + @Override public void execute() throws ServerApiException { try { - final LdapConfigurationResponse response = _ldapManager.deleteConfiguration(hostname); + final LdapConfigurationResponse response = _ldapManager.deleteConfiguration(this); response.setObjectName("LdapDeleteConfiguration"); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java index c38dd7031fe..de8bafeeae6 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LdapImportUsersCmd.java @@ -141,9 +141,9 @@ public class LdapImportUsersCmd extends BaseListCmd { try { if (StringUtils.isNotBlank(groupName)) { - users = _ldapManager.getUsersInGroup(groupName); + users = _ldapManager.getUsersInGroup(groupName, domainId); } else { - users = _ldapManager.getUsers(); + users = _ldapManager.getUsers(domainId); } } catch (NoLdapUserMatchingQueryException ex) { users = new ArrayList(); 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 index 050fb36cb19..db6318e6b2c 100644 --- 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 @@ -21,6 +21,8 @@ import java.util.List; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -44,12 +46,15 @@ public class LdapListConfigurationCmd extends BaseListCmd { @Inject private LdapManager _ldapManager; - @Parameter(name = "hostname", type = CommandType.STRING, required = false, description = "Hostname") + @Parameter(name = ApiConstants. HOST_NAME, type = CommandType.STRING, required = false, description = "Hostname") private String hostname; - @Parameter(name = "port", type = CommandType.INTEGER, required = false, description = "Port") + @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "Port") private int port; + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain") + private Long domainId; + public LdapListConfigurationCmd() { super(); } @@ -97,6 +102,10 @@ public class LdapListConfigurationCmd extends BaseListCmd { return port; } + public Long getDomainId() { + return domainId; + } + public void setHostname(final String hostname) { this.hostname = hostname; } @@ -104,4 +113,8 @@ public class LdapListConfigurationCmd extends BaseListCmd { public void setPort(final int port) { this.port = port; } + + public void setDomainId(final Long domainId) { + this.domainId = domainId; + } } 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 index e655f5f4ac0..b2266dc8fd3 100644 --- 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 @@ -83,7 +83,7 @@ public class LdapListUsersCmd extends BaseListCmd { List ldapResponses = null; final ListResponse response = new ListResponse(); try { - final List users = _ldapManager.getUsers(); + final List users = _ldapManager.getUsers(null); ldapResponses = createLdapUserResponse(users); } catch (final NoLdapUserMatchingQueryException ex) { ldapResponses = new ArrayList(); diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkAccountToLdapCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkAccountToLdapCmd.java new file mode 100644 index 00000000000..52adc664ff8 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkAccountToLdapCmd.java @@ -0,0 +1,142 @@ +/* + * 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 com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.acl.RoleType; +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.DomainResponse; +import org.apache.cloudstack.api.response.LinkAccountToLdapResponse; +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.util.UUID; + +@APICommand(name = LinkAccountToLdapCmd.APINAME, description = "link a cloudstack account to a group or OU in ldap", responseObject = LinkDomainToLdapResponse.class, since = "4.11.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin,RoleType.DomainAdmin}) +public class LinkAccountToLdapCmd extends BaseCmd { + public static final Logger LOGGER = Logger.getLogger(LinkAccountToLdapCmd.class.getName()); + public static final String APINAME = "linkAccountToLdap"; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "The id of the domain that is to contain the linked account.") + private Long domainId; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = false, description = "type of the ldap name. GROUP or OU, defaults to GROUP") + private String type; + + @Parameter(name = ApiConstants.LDAP_DOMAIN, type = CommandType.STRING, required = true, description = "name of the group or OU in LDAP") + private String ldapDomain; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "name of the account, it will be created if it does not exist") + private String accountName; + + @Parameter(name = ApiConstants.ADMIN, type = CommandType.STRING, required = false, description = "domain admin username in LDAP ") + private String admin; + + @Parameter(name = ApiConstants.ACCOUNT_TYPE, type = CommandType.SHORT, required = true, description = "Type of the account to auto import. Specify 0 for user and 2 for " + + "domain admin") + private short accountType; + + @Inject + private LdapManager _ldapManager; + + @Override + public void execute() throws ServerApiException { + try { + LinkAccountToLdapResponse response = _ldapManager.linkAccountToLdap(this); + if (admin != null) { + LdapUser ldapUser = null; + try { + ldapUser = _ldapManager.getUser(admin, type, ldapDomain, domainId); + } catch (NoLdapUserMatchingQueryException e) { + LOGGER.debug("no ldap user matching username " + admin + " in the given group/ou", e); + } + if (ldapUser != null && !ldapUser.isDisabled()) { + Account account = _accountService.getActiveAccountByName(admin, domainId); + if (account == null) { + try { + UserAccount userAccount = _accountService + .createUserAccount(admin, "", ldapUser.getFirstname(), ldapUser.getLastname(), ldapUser.getEmail(), null, admin, Account.ACCOUNT_TYPE_DOMAIN_ADMIN, RoleType.DomainAdmin.getId(), domainId, null, null, UUID.randomUUID().toString(), + UUID.randomUUID().toString(), User.Source.LDAP); + response.setAdminId(String.valueOf(userAccount.getAccountId())); + LOGGER.info("created an account with name " + admin + " in the given domain " + domainId); + } catch (Exception e) { + LOGGER.info("an exception occurred while creating account with name " + admin + " in domain " + domainId, e); + } + } else { + LOGGER.debug("an account with name " + admin + " already exists in the domain " + domainId); + } + } else { + LOGGER.debug("ldap user with username " + admin + " is disabled in the given group/ou"); + } + } + response.setObjectName(APINAME); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (final InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.toString()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + public Long getDomainId() { + return domainId; + } + + public String getType() { + return type; + } + + public String getLdapDomain() { + return ldapDomain; + } + + public String getAccountName() { + return accountName; + } + + public String getAdmin() { + return admin; + } + + public short getAccountType() { + return accountType; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkDomainToLdapCmd.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkDomainToLdapCmd.java new file mode 100644 index 00000000000..00140952051 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/command/LinkDomainToLdapCmd.java @@ -0,0 +1,141 @@ +/* + * 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 com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.acl.RoleType; +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.DomainResponse; +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse; +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; + +import java.util.UUID; + +@APICommand(name = "linkDomainToLdap", description = "link an existing cloudstack domain to group or OU in ldap", responseObject = LinkDomainToLdapResponse.class, since = "4.6.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class LinkDomainToLdapCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(LinkDomainToLdapCmd.class.getName()); + private static final String s_name = "linkdomaintoldapresponse"; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "The id of the domain which has to be " + + "linked to LDAP.") + private Long domainId; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "type of the ldap name. GROUP or OU") + private String type; + + @Parameter(name = ApiConstants.LDAP_DOMAIN, type = CommandType.STRING, required = true, description = "name of the group or OU in LDAP") + private String ldapDomain; + + @Deprecated + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name of the group or OU in LDAP") + private String name; + + @Parameter(name = ApiConstants.ADMIN, type = CommandType.STRING, required = false, description = "domain admin username in LDAP ") + private String admin; + + @Parameter(name = ApiConstants.ACCOUNT_TYPE, type = CommandType.SHORT, required = true, description = "Type of the account to auto import. Specify 0 for user and 2 for " + + "domain admin") + private short accountType; + + @Inject + private LdapManager _ldapManager; + + public Long getDomainId() { + return domainId; + } + + public String getType() { + return type; + } + + public String getLdapDomain() { + return ldapDomain == null ? name : ldapDomain; + } + + public String getAdmin() { + return admin; + } + + public short getAccountType() { + return accountType; + } + + + @Override + public void execute() throws ServerApiException { + try { + LinkDomainToLdapResponse response = _ldapManager.linkDomainToLdap(this); + if(admin!=null) { + LdapUser ldapUser = null; + try { + ldapUser = _ldapManager.getUser(admin, type, getLdapDomain(), domainId); + } catch (NoLdapUserMatchingQueryException e) { + s_logger.debug("no ldap user matching username " + admin + " in the given group/ou", e); + } + if (ldapUser != null && !ldapUser.isDisabled()) { + Account account = _accountService.getActiveAccountByName(admin, domainId); + if (account == null) { + try { + UserAccount userAccount = _accountService.createUserAccount(admin, "", ldapUser.getFirstname(), ldapUser.getLastname(), ldapUser.getEmail(), null, + admin, Account.ACCOUNT_TYPE_DOMAIN_ADMIN, RoleType.DomainAdmin.getId(), domainId, null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), User.Source.LDAP); + response.setAdminId(String.valueOf(userAccount.getAccountId())); + s_logger.info("created an account with name " + admin + " in the given domain " + domainId); + } catch (Exception e) { + s_logger.info("an exception occurred while creating account with name " + admin +" in domain " + domainId, e); + } + } else { + s_logger.debug("an account with name " + admin + " already exists in the domain " + domainId); + } + } else { + s_logger.debug("ldap user with username "+admin+" is disabled in the given group/ou"); + } + } + response.setObjectName("LinkDomainToLdap"); + 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/response/LdapConfigurationResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LdapConfigurationResponse.java index a4e47828844..c6d2bf38cb2 100644 --- 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 @@ -18,31 +18,44 @@ package org.apache.cloudstack.api.response; import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import com.cloud.serializer.Param; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.ldap.LdapConfiguration; +@EntityReference(value = LdapConfiguration.class) public class LdapConfigurationResponse extends BaseResponse { - @SerializedName("hostname") - @Param(description = "hostname") + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "name of the host running the ldap server") private String hostname; - @SerializedName("port") - @Param(description = "port") + @SerializedName(ApiConstants.PORT) + @Param(description = "port teh ldap server is running on") private int port; + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "linked domain") + private String domainId; + public LdapConfigurationResponse() { super(); } public LdapConfigurationResponse(final String hostname) { super(); - this.hostname = hostname; + setHostname(hostname); } public LdapConfigurationResponse(final String hostname, final int port) { - this.hostname = hostname; - this.port = port; + this(hostname); + setPort(port); + } + + public LdapConfigurationResponse(final String hostname, final int port, final String domainId) { + this(hostname, port); + setDomainId(domainId); } public String getHostname() { @@ -60,4 +73,12 @@ public class LdapConfigurationResponse extends BaseResponse { public void setPort(final int port) { this.port = port; } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } } \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkAccountToLdapResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkAccountToLdapResponse.java new file mode 100644 index 00000000000..23456e71641 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkAccountToLdapResponse.java @@ -0,0 +1,85 @@ +/* + * 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class LinkAccountToLdapResponse extends BaseResponse { + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "id of the Domain which is linked to LDAP") + private String domainId; + + @SerializedName(ApiConstants.LDAP_DOMAIN) + @Param(description = "name of the group or OU in LDAP which is linked to the domain") + private String ldapDomain; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "type of the name in LDAP which is linke to the domain") + private String type; + + @SerializedName(ApiConstants.ACCOUNT_TYPE) + @Param(description = "Type of the account to auto import") + private short accountType; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "Domain Admin accountId that is created") + private String adminId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "name of the account") + private String accountName; + + + public LinkAccountToLdapResponse(String domainId, String type, String ldapDomain, short accountType, String adminId, String accountName) { + this.domainId = domainId; + this.type = type; + this.ldapDomain = ldapDomain; + this.accountType = accountType; + this.adminId = adminId; + this.accountName = accountName; + } + + public String getDomainId() { + return domainId; + } + + public String getLdapDomain() { + return ldapDomain; + } + + public String getType() { + return type; + } + + public short getAccountType() { + return accountType; + } + + public String getAdminId() { + return adminId; + } + + public void setAdminId(String adminId) { + this.adminId = adminId; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkDomainToLdapResponse.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkDomainToLdapResponse.java new file mode 100644 index 00000000000..d6d4b55e257 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/api/response/LinkDomainToLdapResponse.java @@ -0,0 +1,84 @@ +/* + * 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class LinkDomainToLdapResponse extends BaseResponse { + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "id of the Domain which is linked to LDAP") + private String domainId; + + @Deprecated + @SerializedName(ApiConstants.NAME) + @Param(description = "name of the group or OU in LDAP which is linked to the domain") + private String name; + + @SerializedName(ApiConstants.LDAP_DOMAIN) + @Param(description = "name of the group or OU in LDAP which is linked to the domain") + private String ldapDomain; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "type of the name in LDAP which is linke to the domain") + private String type; + + @SerializedName(ApiConstants.ACCOUNT_TYPE) + @Param(description = "Type of the account to auto import") + private short accountType; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "Domain Admin accountId that is created") + private String adminId; + + public LinkDomainToLdapResponse(String domainId, String type, String ldapDomain, short accountType) { + this.domainId = domainId; + this.name = ldapDomain; + this.ldapDomain = ldapDomain; + this.type = type; + this.accountType = accountType; + } + + public String getDomainId() { + return domainId; + } + + public String getLdapDomain() { + return ldapDomain == null ? name : ldapDomain; + } + + public String getType() { + return type; + } + + public short getAccountType() { + return accountType; + } + + public String getAdminId() { + return adminId; + } + + public void setAdminId(String adminId) { + this.adminId = adminId; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java new file mode 100644 index 00000000000..e844df57c1c --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java @@ -0,0 +1,104 @@ +/* + * 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.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +public class ADLdapUserManagerImpl extends OpenLdapUserManagerImpl implements LdapUserManager { + public static final Logger s_logger = Logger.getLogger(ADLdapUserManagerImpl.class.getName()); + private static final String MICROSOFT_AD_NESTED_MEMBERS_FILTER = "memberOf:1.2.840.113556.1.4.1941:"; + private static final String MICROSOFT_AD_MEMBERS_FILTER = "memberOf"; + + @Override + public List getUsersInGroup(String groupName, LdapContext context, Long domainId) throws NamingException { + if (StringUtils.isBlank(groupName)) { + throw new IllegalArgumentException("ldap group name cannot be blank"); + } + + String basedn = _ldapConfiguration.getBaseDn(domainId); + if (StringUtils.isBlank(basedn)) { + throw new IllegalArgumentException("ldap basedn is not configured"); + } + + final SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(_ldapConfiguration.getScope()); + searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId)); + + NamingEnumeration results = context.search(basedn, generateADGroupSearchFilter(groupName, domainId), searchControls); + final List users = new ArrayList(); + while (results.hasMoreElements()) { + final SearchResult result = results.nextElement(); + users.add(createUser(result, domainId)); + } + return users; + } + + private String generateADGroupSearchFilter(String groupName, Long domainId) { + final StringBuilder userObjectFilter = new StringBuilder(); + userObjectFilter.append("(objectClass="); + userObjectFilter.append(_ldapConfiguration.getUserObject(domainId)); + userObjectFilter.append(")"); + + final StringBuilder memberOfFilter = new StringBuilder(); + String groupCnName = _ldapConfiguration.getCommonNameAttribute() + "=" +groupName + "," + _ldapConfiguration.getBaseDn(domainId); + memberOfFilter.append("(").append(getMemberOfAttribute(domainId)).append("="); + memberOfFilter.append(groupCnName); + memberOfFilter.append(")"); + + final StringBuilder result = new StringBuilder(); + result.append("(&"); + result.append(userObjectFilter); + result.append(memberOfFilter); + result.append(")"); + + s_logger.debug("group search filter = " + result); + return result.toString(); + } + + protected boolean isUserDisabled(SearchResult result) throws NamingException { + boolean isDisabledUser = false; + String userAccountControl = LdapUtils.getAttributeValue(result.getAttributes(), _ldapConfiguration.getUserAccountControlAttribute()); + if (userAccountControl != null) { + int control = Integer.parseInt(userAccountControl); + // second bit represents disabled user flag in AD + if ((control & 2) > 0) { + isDisabledUser = true; + } + } + return isDisabledUser; + } + + protected String getMemberOfAttribute(final Long domainId) { + if(_ldapConfiguration.isNestedGroupsEnabled(domainId)) { + return MICROSOFT_AD_NESTED_MEMBERS_FILTER; + } else { + return MICROSOFT_AD_MEMBERS_FILTER; + } + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java index 8c6820f8458..d3452314cf5 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapAuthenticator.java @@ -16,23 +16,35 @@ // under the License. package org.apache.cloudstack.ldap; -import com.cloud.server.auth.DefaultUserAuthenticator; -import com.cloud.user.UserAccount; -import com.cloud.user.dao.UserAccountDao; -import com.cloud.utils.Pair; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import javax.inject.Inject; -import java.util.Map; +import com.cloud.server.auth.UserAuthenticator; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import com.cloud.user.dao.UserAccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; -public class LdapAuthenticator extends DefaultUserAuthenticator { +public class LdapAuthenticator extends AdapterBase implements UserAuthenticator { private static final Logger s_logger = Logger.getLogger(LdapAuthenticator.class.getName()); @Inject private LdapManager _ldapManager; @Inject private UserAccountDao _userAccountDao; + @Inject + private AccountManager _accountManager; public LdapAuthenticator() { super(); @@ -46,27 +58,214 @@ public class LdapAuthenticator extends DefaultUserAuthenticator { @Override public Pair authenticate(final String username, final String password, final Long domainId, final Map requestParameters) { + Pair rc = new Pair(false, null); + // TODO not allowing an empty password is a policy we shouldn't decide on. A private cloud may well want to allow this. if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { s_logger.debug("Username or Password cannot be empty"); - return new Pair(false, null); + return rc; } - final UserAccount user = _userAccountDao.getUserAccount(username, domainId); - - if (user == null) { - s_logger.debug("Unable to find user with " + username + " in domain " + domainId); - return new Pair(false, null); - } else if (_ldapManager.isLdapEnabled()) { - boolean result = _ldapManager.canAuthenticate(username, password); - ActionOnFailedAuthentication action = null; - if (result == false) { - action = ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT; + if (_ldapManager.isLdapEnabled()) { + final UserAccount user = _userAccountDao.getUserAccount(username, domainId); + List ldapTrustMapVOs = _ldapManager.getDomainLinkage(domainId); + if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) { + if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) { + // We have a single mapping of a domain to an ldap group or ou + return authenticate(username, password, domainId, user, ldapTrustMapVOs.get(0)); + } else { + // we are dealing with mapping of accounts in a domain to ldap groups + return authenticate(username, password, domainId, user, ldapTrustMapVOs); + } + } else { + //domain is not linked to ldap follow normal authentication + return authenticate(username, password, domainId, user); } - return new Pair(result, action); + } + return rc; + } + + /** + * checks if the user exists in ldap and create in cloudstack if needed. + * + * @param username login id + * @param password pass phrase + * @param domainId domain the user is trying to log on to + * @param userAccount cloudstack user object + * @param ldapTrustMapVOs the trust mappings of accounts in the domain to ldap groups + * @return false if the ldap user object does not exist, is not mapped to an account, is mapped to multiple accounts or if authenitication fails + */ + private Pair authenticate(String username, String password, Long domainId, UserAccount userAccount, List ldapTrustMapVOs) { + if(s_logger.isDebugEnabled()) { + s_logger.debug("trying to log on '" + username + "' to ldap in linked account"); + } + Pair rc = new Pair(false, null); + try { + LdapUser ldapUser = _ldapManager.getUser(username, domainId); + List memberships = ldapUser.getMemberships(); + List mappedGroups = getMappedGroups(ldapTrustMapVOs); + mappedGroups.retainAll(memberships); + // check membership, there must be only one match in this domain + if(ldapUser.isDisabled()) { + logAndDisable(userAccount, "attempt to log on using disabled ldap user " + userAccount.getUsername(), false); + } else if(mappedGroups.size() > 1) { + logAndDisable(userAccount, "user '" + username + "' is mapped to more then one account in domain and will be disabled.", false); + } else if(mappedGroups.size() < 1) { + logAndDisable(userAccount, "user '" + username + "' is not mapped to an account in domain and will be removed.", true); + } else { + // a valid ldap configured user exists + LdapTrustMapVO mapping = _ldapManager.getLinkedLdapGroup(domainId,mappedGroups.get(0)); + // we could now assert that ldapTrustMapVOs.contains(mapping); + // createUser in Account can only be done by account name not by account id + String accountName = _accountManager.getAccount(mapping.getAccountId()).getAccountName(); + rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId)); + // for security reasons we keep processing on faulty login attempt to not give a way information on userid existence + if (userAccount == null) { + // new user that is in ldap; authenticate and create + User user = _accountManager.createUser(username, "", ldapUser.getFirstname(), ldapUser.getLastname(), ldapUser.getEmail(), null, accountName, + domainId, UUID.randomUUID().toString(), User.Source.LDAP); + /* expected error conditions: + * + * caught in APIServlet: CloudRuntimeException("The domain " + domainId + " does not exist; unable to create user"); + * caught in APIServlet: CloudRuntimeException("The user cannot be created as domain " + domain.getName() + " is being deleted"); + * would have been thrown above: InvalidParameterValueException("Unable to find account " + accountName + " in domain id=" + domainId + " to create user"); + * we are system user: PermissionDeniedException("Account id : " + account.getId() + " is a system account, can't add a user to it"); + * serious and must be thrown: CloudRuntimeException("The user " + userName + " already exists in domain " + domainId); + * fatal system error and must be thrown: CloudRuntimeException("Failed to encode password"); + */ + userAccount = _accountManager.getUserAccountById(user.getId()); + } else { + // not a new user, check if mapped group has changed + if(userAccount.getAccountId() != mapping.getAccountId()) { + _accountManager.moveUser(userAccount.getId(),userAccount.getDomainId(),mapping.getAccountId()); + } + // else { the user hasn't changed in ldap, the ldap group stayed the same, hurray, pass, fun thou self a lot of fun } + } + } + } catch (NoLdapUserMatchingQueryException e) { + s_logger.debug(e.getMessage()); + disableUserInCloudStack(userAccount); + } + + return rc; + } + + private void logAndDisable(UserAccount userAccount, String msg, boolean remove) { + if (s_logger.isInfoEnabled()) { + s_logger.info(msg); + } + if(remove) { + removeUserInCloudStack(userAccount); } else { - return new Pair(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); + disableUserInCloudStack(userAccount); + } + } + + private List getMappedGroups(List ldapTrustMapVOs) { + List groups = new ArrayList<>(); + for (LdapTrustMapVO vo : ldapTrustMapVOs) { + groups.add(vo.getName()); + } + return groups; + } + + /** + * checks if the user exists in ldap and create in cloudstack if needed + * @param username login id + * @param password pass phrase + * @param domainId domain the user is trying to log on to + * @param user cloudstack user object + * @param ldapTrustMapVO the trust mapping for the domain to the ldap group + * @return false if the ldap user object does not exist or authenitication fails + */ + private Pair authenticate(String username, String password, Long domainId, UserAccount user, LdapTrustMapVO ldapTrustMapVO) { + if(s_logger.isDebugEnabled()) { + s_logger.debug("trying to log on '" + username + "' to ldap in linked domain"); + } + Pair rc = new Pair(false, null); + try { + LdapUser ldapUser = _ldapManager.getUser(username, ldapTrustMapVO.getType().toString(), ldapTrustMapVO.getName(), domainId); + final short accountType = ldapTrustMapVO.getAccountType(); + processLdapUser(password, domainId, user, rc, ldapUser, accountType); + } catch (NoLdapUserMatchingQueryException e) { + s_logger.debug(e.getMessage()); + } + return rc; + } + + private void processLdapUser(String password, Long domainId, UserAccount user, Pair rc, LdapUser ldapUser, short accountType) { + if(!ldapUser.isDisabled()) { + rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId)); + if(rc.first()) { + if(user == null) { + // import user to cloudstack + createCloudStackUserAccount(ldapUser, domainId, accountType); + } else { + enableUserInCloudStack(user); + } + } else if(user != null) { + rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); + } + } else { + //disable user in cloudstack + disableUserInCloudStack(user); + } + } + + /** + * checks if the user is configured both in ldap and in cloudstack. + * @param username login id + * @param password pass phrase + * @param domainId domain the user is trying to log on to + * @param user cloudstack user object + * @return false if either user object does not exist or authenitication fails + */ + private Pair authenticate(String username, String password, Long domainId, UserAccount user) { + if(s_logger.isDebugEnabled()) { + s_logger.debug("trying to log on '" + username + "' as linked ldap user"); + } + boolean result = false; + + if(user != null ) { + try { + LdapUser ldapUser = _ldapManager.getUser(username, domainId); + if(!ldapUser.isDisabled()) { + result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId); + } else { + s_logger.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap"); + } + } catch (NoLdapUserMatchingQueryException e) { + s_logger.debug(e.getMessage()); + } + } + return (!result && user != null) ? + new Pair(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT): + new Pair(false, null); + } + + private void enableUserInCloudStack(UserAccount user) { + if(user != null && (user.getState().equalsIgnoreCase(Account.State.disabled.toString()))) { + _accountManager.enableUser(user.getId()); + } + } + + private void createCloudStackUserAccount(LdapUser user, long domainId, short accountType) { + String username = user.getUsername(); + _accountManager.createUserAccount(username, "", user.getFirstname(), user.getLastname(), user.getEmail(), null, username, + accountType, RoleType.getByAccountType(accountType).getId(), domainId, null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), User.Source.LDAP); + } + + private void disableUserInCloudStack(UserAccount user) { + if (user != null) { + _accountManager.disableUser(user.getId()); + } + } + + private void removeUserInCloudStack(UserAccount user) { + if (user != null) { + _accountManager.disableUser(user.getId()); } } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java index c171ebfcc0b..b87478056bc 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfiguration.java @@ -21,80 +21,224 @@ import java.util.List; import javax.inject.Inject; import javax.naming.directory.SearchControls; -import org.apache.cloudstack.api.command.LdapListConfigurationCmd; +import com.cloud.utils.Pair; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; - -import com.cloud.utils.Pair; +import org.apache.cloudstack.ldap.dao.LdapConfigurationDao; public class LdapConfiguration implements Configurable{ private final static String factory = "com.sun.jndi.ldap.LdapCtxFactory"; - private static final ConfigKey ldapReadTimeout = new ConfigKey(Long.class, "ldap.read.timeout", "Advanced", "1000", - "LDAP connection Timeout in milli sec", true, ConfigKey.Scope.Global, 1l); + private static final ConfigKey ldapReadTimeout = new ConfigKey( + Long.class, + "ldap.read.timeout", + "Advanced", + "1000", + "LDAP connection Timeout in milli sec", + true, + ConfigKey.Scope.Domain, + 1l); - private static final ConfigKey ldapPageSize = new ConfigKey(Integer.class, "ldap.request.page.size", "Advanced", "1000", - "page size sent to ldap server on each request to get user", true, ConfigKey.Scope.Global, 1); + private static final ConfigKey ldapPageSize = new ConfigKey( + Integer.class, + "ldap.request.page.size", + "Advanced", + "1000", + "page size sent to ldap server on each request to get user", + true, + ConfigKey.Scope.Domain, + 1); + + private static final ConfigKey ldapEnableNestedGroups = new ConfigKey( + "Advanced", + Boolean.class, + "ldap.nested.groups.enable", + "true", + "if true, nested groups will also be queried", + true, + ConfigKey.Scope.Domain); + + private static final ConfigKey ldapMemberOfAttribute = new ConfigKey( + "Advanced", + String.class, + "ldap.user.memberof.attribute", + "memberof", + "the reverse membership attibute for group members", + true, + ConfigKey.Scope.Domain); + + private static final ConfigKey ldapProvider = new ConfigKey( + "Advanced", + String.class, + "ldap.provider", + "openldap", + "ldap provider ex:openldap, microsoftad", + true, + ConfigKey.Scope.Domain); + + private static final ConfigKey ldapBaseDn = new ConfigKey( + "Advanced", + String.class, + "ldap.basedn", + null, + "Sets the basedn for LDAP", + true, + ConfigKey.Scope.Domain); + + private static final ConfigKey ldapBindPassword = new ConfigKey( + "Advanced", + String.class, + "ldap.bind.password", + null, + "Sets the bind password for LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapBindPrincipal = new ConfigKey( + "Advanced", + String.class, + "ldap.bind.principal", + null, + "Sets the bind principal for LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapEmailAttribute = new ConfigKey( + "Advanced", + String.class, + "ldap.email.attribute", + "mail", + "Sets the email attribute used within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapFirstnameAttribute = new ConfigKey( + "Advanced", + String.class, + "ldap.firstname.attribute", + "givenname", + "Sets the firstname attribute used within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapLastnameAttribute = new ConfigKey( + "Advanced", + String.class, "ldap.lastname.attribute", + "sn", + "Sets the lastname attribute used within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapUsernameAttribute = new ConfigKey( + "Advanced", + String.class, + "ldap.username.attribute", + "uid", + "Sets the username attribute used within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapUserObject = new ConfigKey( + "Advanced", + String.class, + "ldap.user.object", + "inetOrgPerson", + "Sets the object type of users within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapSearchGroupPrinciple = new ConfigKey( + "Advanced", + String.class, + "ldap.search.group.principle", + null, + "Sets the principle of the group that users must be a member of", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapGroupObject = new ConfigKey( + "Advanced", + String.class, + "ldap.group.object", + "groupOfUniqueNames", + "Sets the object type of groups within LDAP", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapGroupUniqueMemberAttribute = new ConfigKey( + "Advanced", + String.class, + "ldap.group.user.uniquemember", + "uniquemember", + "Sets the attribute for uniquemembers within a group", + true, + ConfigKey.Scope.Domain); + + private static final ConfigKey ldapTrustStore = new ConfigKey( + "Advanced", + String.class, + "ldap.truststore", + null, + "Sets the path to the truststore to use for SSL", + true, + ConfigKey.Scope.Domain); + private static final ConfigKey ldapTrustStorePassword = new ConfigKey( + "Advanced", + String.class, + "ldap.truststore.password", + null, + "Sets the password for the truststore", + true, + ConfigKey.Scope.Domain); private final static int scope = SearchControls.SUBTREE_SCOPE; @Inject - private ConfigurationDao _configDao; - - @Inject - private LdapManager _ldapManager; + private LdapConfigurationDao _ldapConfigurationDao; public LdapConfiguration() { } - public LdapConfiguration(final ConfigurationDao configDao, final LdapManager ldapManager) { - _configDao = configDao; - _ldapManager = ldapManager; + public LdapConfiguration(final LdapConfigurationDao ldapConfigurationDao) { + _ldapConfigurationDao = ldapConfigurationDao; } - public String getAuthentication() { - if ((getBindPrincipal() == null) && (getBindPassword() == null)) { + @Deprecated + public LdapConfiguration(final ConfigurationDao configDao, final LdapConfigurationDao ldapConfigurationDao) { + _ldapConfigurationDao = ldapConfigurationDao; + } + + public String getAuthentication(final Long domainId) { + if ((getBindPrincipal(domainId) == null) && (getBindPassword(domainId) == null)) { return "none"; } else { return "simple"; } } - public String getBaseDn() { - return _configDao.getValue("ldap.basedn"); + public String getBaseDn(final Long domainId) { + return ldapBaseDn.valueIn(domainId); } - public String getBindPassword() { - return _configDao.getValue("ldap.bind.password"); + public String getBindPassword(final Long domainId) { + return ldapBindPassword.valueIn(domainId); } - public String getBindPrincipal() { - return _configDao.getValue("ldap.bind.principal"); + public String getBindPrincipal(final Long domainId) { + return ldapBindPrincipal.valueIn(domainId); } - public String getEmailAttribute() { - final String emailAttribute = _configDao.getValue("ldap.email.attribute"); - return emailAttribute == null ? "mail" : emailAttribute; + public String getEmailAttribute(final Long domainId) { + return ldapEmailAttribute.valueIn(domainId); } public String getFactory() { return factory; } - public String getFirstnameAttribute() { - final String firstnameAttribute = _configDao.getValue("ldap.firstname.attribute"); - return firstnameAttribute == null ? "givenname" : firstnameAttribute; + public String getFirstnameAttribute(final Long domainId) { + return ldapFirstnameAttribute.valueIn(domainId); } - public String getLastnameAttribute() { - final String lastnameAttribute = _configDao.getValue("ldap.lastname.attribute"); - return lastnameAttribute == null ? "sn" : lastnameAttribute; + public String getLastnameAttribute(final Long domainId) { + return ldapLastnameAttribute.valueIn(domainId); } - public String getProviderUrl() { + public String getProviderUrl(final Long domainId) { final String protocol = getSSLStatus() == true ? "ldaps://" : "ldap://"; - final Pair, Integer> result = _ldapManager.listConfigurations(new LdapListConfigurationCmd(_ldapManager)); + final Pair, Integer> result = _ldapConfigurationDao.searchConfigurations(null, 0, domainId); final StringBuilder providerUrls = new StringBuilder(); String delim = ""; for (final LdapConfigurationVO resource : result.first()) { @@ -105,16 +249,24 @@ public class LdapConfiguration implements Configurable{ return providerUrls.toString(); } - public String[] getReturnAttributes() { - return new String[] {getUsernameAttribute(), getEmailAttribute(), getFirstnameAttribute(), getLastnameAttribute(), getCommonNameAttribute()}; + public String[] getReturnAttributes(final Long domainId) { + return new String[] { + getUsernameAttribute(domainId), + getEmailAttribute(domainId), + getFirstnameAttribute(domainId), + getLastnameAttribute(domainId), + getCommonNameAttribute(), + getUserAccountControlAttribute(), + getUserMemberOfAttribute(domainId) + }; } public int getScope() { return scope; } - public String getSearchGroupPrinciple() { - return _configDao.getValue("ldap.search.group.principle"); + public String getSearchGroupPrinciple(final Long domainId) { + return ldapSearchGroupPrinciple.valueIn(domainId); } public boolean getSSLStatus() { @@ -126,43 +278,64 @@ public class LdapConfiguration implements Configurable{ } public String getTrustStore() { - return _configDao.getValue("ldap.truststore"); + return ldapTrustStore.value(); } public String getTrustStorePassword() { - return _configDao.getValue("ldap.truststore.password"); + return ldapTrustStorePassword.value(); } - public String getUsernameAttribute() { - final String usernameAttribute = _configDao.getValue("ldap.username.attribute"); - return usernameAttribute == null ? "uid" : usernameAttribute; + public String getUsernameAttribute(final Long domainId) { + return ldapUsernameAttribute.valueIn(domainId); } - public String getUserObject() { - final String userObject = _configDao.getValue("ldap.user.object"); - return userObject == null ? "inetOrgPerson" : userObject; + public String getUserObject(final Long domainId) { + return ldapUserObject.valueIn(domainId); } - public String getGroupObject() { - final String groupObject = _configDao.getValue("ldap.group.object"); - return groupObject == null ? "groupOfUniqueNames" : groupObject; + public String getGroupObject(final Long domainId) { + return ldapGroupObject.valueIn(domainId); } - public String getGroupUniqueMemeberAttribute() { - final String uniqueMemberAttribute = _configDao.getValue("ldap.group.user.uniquemember"); - return uniqueMemberAttribute == null ? "uniquemember" : uniqueMemberAttribute; + public String getGroupUniqueMemberAttribute(final Long domainId) { + return ldapGroupUniqueMemberAttribute.valueIn(domainId); } + // TODO remove hard-coding public String getCommonNameAttribute() { return "cn"; } - public Long getReadTimeout() { - return ldapReadTimeout.value(); + // TODO remove hard-coding + public String getUserAccountControlAttribute() { + return "userAccountControl"; } - public Integer getLdapPageSize() { - return ldapPageSize.value(); + public Long getReadTimeout(final Long domainId) { + return ldapReadTimeout.valueIn(domainId); + } + + public Integer getLdapPageSize(final Long domainId) { + return ldapPageSize.valueIn(domainId); + } + + public LdapUserManager.Provider getLdapProvider(final Long domainId) { + LdapUserManager.Provider provider; + try { + provider = LdapUserManager.Provider.valueOf(ldapProvider.valueIn(domainId).toUpperCase()); + } catch (IllegalArgumentException ex) { + //openldap is the default + provider = LdapUserManager.Provider.OPENLDAP; + } + return provider; + } + + public boolean isNestedGroupsEnabled(final Long domainId) { + return ldapEnableNestedGroups.valueIn(domainId); + } + + public static String getUserMemberOfAttribute(final Long domainId) { + return ldapMemberOfAttribute.valueIn(domainId); } @Override @@ -172,6 +345,25 @@ public class LdapConfiguration implements Configurable{ @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {ldapReadTimeout, ldapPageSize}; + return new ConfigKey[]{ + ldapReadTimeout, + ldapPageSize, + ldapProvider, + ldapEnableNestedGroups, + ldapBaseDn, + ldapBindPassword, + ldapBindPrincipal, + ldapEmailAttribute, + ldapFirstnameAttribute, + ldapLastnameAttribute, + ldapUsernameAttribute, + ldapUserObject, + ldapSearchGroupPrinciple, + ldapGroupObject, + ldapGroupUniqueMemberAttribute, + ldapTrustStore, + ldapTrustStorePassword, + ldapMemberOfAttribute + }; } -} \ 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 index 488e7f44485..e7db88675ab 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfigurationVO.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapConfigurationVO.java @@ -39,12 +39,16 @@ public class LdapConfigurationVO implements InternalIdentity { @Column(name = "port") private int port; + @Column(name = "domain_id") + private Long domainId; + public LdapConfigurationVO() { } - public LdapConfigurationVO(final String hostname, final int port) { + public LdapConfigurationVO(final String hostname, final int port, final Long domainId) { this.hostname = hostname; this.port = port; + this.domainId = domainId; } public String getHostname() { @@ -60,6 +64,10 @@ public class LdapConfigurationVO implements InternalIdentity { return port; } + public Long getDomainId() { + return domainId; + } + public void setId(final long id) { this.id = id; } 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 index 9e27fff078e..b141f053008 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapContextFactory.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapContextFactory.java @@ -40,29 +40,31 @@ public class LdapContextFactory { _ldapConfiguration = ldapConfiguration; } - public LdapContext createBindContext() throws NamingException, IOException { - return createBindContext(null); + // TODO add optional domain (optional only for backwards compatibility) + public LdapContext createBindContext(Long domainId) throws NamingException, IOException { + return createBindContext(null, domainId); } - public LdapContext createBindContext(final String providerUrl) throws NamingException, IOException { - final String bindPrincipal = _ldapConfiguration.getBindPrincipal(); - final String bindPassword = _ldapConfiguration.getBindPassword(); - return createInitialDirContext(bindPrincipal, bindPassword, providerUrl, true); + // TODO add optional domain (optional only for backwards compatibility) + public LdapContext createBindContext(final String providerUrl, Long domainId) throws NamingException, IOException { + final String bindPrincipal = _ldapConfiguration.getBindPrincipal(domainId); + final String bindPassword = _ldapConfiguration.getBindPassword(domainId); + return createInitialDirContext(bindPrincipal, bindPassword, providerUrl, true, domainId); } - private LdapContext createInitialDirContext(final String principal, final String password, final boolean isSystemContext) throws NamingException, IOException { - return createInitialDirContext(principal, password, null, isSystemContext); + private LdapContext createInitialDirContext(final String principal, final String password, final boolean isSystemContext, Long domainId) throws NamingException, IOException { + return createInitialDirContext(principal, password, null, isSystemContext, domainId); } - private LdapContext createInitialDirContext(final String principal, final String password, final String providerUrl, final boolean isSystemContext) + private LdapContext createInitialDirContext(final String principal, final String password, final String providerUrl, final boolean isSystemContext, Long domainId) throws NamingException, IOException { - Hashtable environment = getEnvironment(principal, password, providerUrl, isSystemContext); + Hashtable environment = getEnvironment(principal, password, providerUrl, isSystemContext, domainId); s_logger.debug("initializing ldap with provider url: " + environment.get(Context.PROVIDER_URL)); return new InitialLdapContext(environment, null); } - public LdapContext createUserContext(final String principal, final String password) throws NamingException, IOException { - return createInitialDirContext(principal, password, false); + public LdapContext createUserContext(final String principal, final String password, Long domainId) throws NamingException, IOException { + return createInitialDirContext(principal, password, false, domainId); } private void enableSSL(final Hashtable environment) { @@ -76,19 +78,19 @@ public class LdapContextFactory { } } - private Hashtable getEnvironment(final String principal, final String password, final String providerUrl, final boolean isSystemContext) { + private Hashtable getEnvironment(final String principal, final String password, final String providerUrl, final boolean isSystemContext, Long domainId) { final String factory = _ldapConfiguration.getFactory(); - final String url = providerUrl == null ? _ldapConfiguration.getProviderUrl() : providerUrl; + final String url = providerUrl == null ? _ldapConfiguration.getProviderUrl(domainId) : 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", _ldapConfiguration.getReadTimeout().toString()); + environment.put("com.sun.jndi.ldap.read.timeout", _ldapConfiguration.getReadTimeout(domainId).toString()); environment.put("com.sun.jndi.ldap.connect.pool", "true"); enableSSL(environment); - setAuthentication(environment, isSystemContext); + setAuthentication(environment, isSystemContext, domainId); if (principal != null) { environment.put(Context.SECURITY_PRINCIPAL, principal); @@ -101,8 +103,8 @@ public class LdapContextFactory { return environment; } - private void setAuthentication(final Hashtable environment, final boolean isSystemContext) { - final String authentication = _ldapConfiguration.getAuthentication(); + private void setAuthentication(final Hashtable environment, final boolean isSystemContext, final Long domainId) { + final String authentication = _ldapConfiguration.getAuthentication(domainId); if ("none".equals(authentication) && !isSystemContext) { environment.put(Context.SECURITY_AUTHENTICATION, "simple"); diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java index 31205c457ad..a49777d43ed 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManager.java @@ -18,7 +18,11 @@ package org.apache.cloudstack.ldap; import java.util.List; +import org.apache.cloudstack.api.command.LdapAddConfigurationCmd; +import org.apache.cloudstack.api.command.LdapDeleteConfigurationCmd; import org.apache.cloudstack.api.command.LdapListConfigurationCmd; +import org.apache.cloudstack.api.command.LinkAccountToLdapCmd; +import org.apache.cloudstack.api.command.LinkDomainToLdapCmd; import org.apache.cloudstack.api.response.LdapConfigurationResponse; import org.apache.cloudstack.api.response.LdapUserResponse; @@ -26,27 +30,53 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.Pair; import com.cloud.utils.component.PluggableService; +import org.apache.cloudstack.api.response.LinkAccountToLdapResponse; +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse; + public interface LdapManager extends PluggableService { - LdapConfigurationResponse addConfiguration(String hostname, int port) throws InvalidParameterValueException; + enum LinkType { GROUP, OU;} - boolean canAuthenticate(String username, String password); + LdapConfigurationResponse addConfiguration(final LdapAddConfigurationCmd cmd) throws InvalidParameterValueException; + + @Deprecated + LdapConfigurationResponse addConfiguration(String hostname, int port, Long domainId) throws InvalidParameterValueException; + + boolean canAuthenticate(String principal, String password, final Long domainId); LdapConfigurationResponse createLdapConfigurationResponse(LdapConfigurationVO configuration); LdapUserResponse createLdapUserResponse(LdapUser user); - LdapConfigurationResponse deleteConfiguration(String hostname) throws InvalidParameterValueException; + LdapConfigurationResponse deleteConfiguration(LdapDeleteConfigurationCmd cmd) throws InvalidParameterValueException; - LdapUser getUser(final String username) throws NoLdapUserMatchingQueryException; + @Deprecated + LdapConfigurationResponse deleteConfiguration(String hostname, int port, Long domainId) throws InvalidParameterValueException; - List getUsers() throws NoLdapUserMatchingQueryException; + // TODO username is only unique withing domain scope (add domain id to call) + LdapUser getUser(final String username, Long domainId) throws NoLdapUserMatchingQueryException; - List getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException; + LdapUser getUser(String username, String type, String name, Long domainId) throws NoLdapUserMatchingQueryException; + + List getUsers(Long domainId) throws NoLdapUserMatchingQueryException; + + List getUsersInGroup(String groupName, Long domainId) throws NoLdapUserMatchingQueryException; boolean isLdapEnabled(); Pair, Integer> listConfigurations(LdapListConfigurationCmd cmd); List searchUsers(String query) throws NoLdapUserMatchingQueryException; + + LinkDomainToLdapResponse linkDomainToLdap(LinkDomainToLdapCmd cmd); + + LdapTrustMapVO getDomainLinkedToLdap(long domainId); + + List getDomainLinkage(long domainId); + + LdapTrustMapVO getAccountLinkedToLdap(long domainId, long accountId); + + LdapTrustMapVO getLinkedLdapGroup(long domainId, String group); + + LinkAccountToLdapResponse linkAccountToLdap(LinkAccountToLdapCmd linkAccountToLdapCmd); } \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java index 87c0443a3c4..5e789f03b90 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -19,15 +19,19 @@ package org.apache.cloudstack.ldap; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.UUID; -import javax.ejb.Local; import javax.inject.Inject; import javax.naming.NamingException; import javax.naming.ldap.LdapContext; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; import org.apache.cloudstack.api.LdapValidator; import org.apache.cloudstack.api.command.LDAPConfigCmd; import org.apache.cloudstack.api.command.LDAPRemoveCmd; @@ -38,70 +42,111 @@ import org.apache.cloudstack.api.command.LdapImportUsersCmd; 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.command.LinkAccountToLdapCmd; +import org.apache.cloudstack.api.command.LinkDomainToLdapCmd; import org.apache.cloudstack.api.response.LdapConfigurationResponse; import org.apache.cloudstack.api.response.LdapUserResponse; +import org.apache.cloudstack.api.response.LinkAccountToLdapResponse; +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse; import org.apache.cloudstack.ldap.dao.LdapConfigurationDao; - -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.utils.Pair; +import org.apache.cloudstack.ldap.dao.LdapTrustMapDao; +import org.apache.commons.lang.Validate; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; @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 DomainDao domainDao; + + @Inject + private AccountDao accountDao; + @Inject private LdapContextFactory _ldapContextFactory; @Inject - private LdapUserManager _ldapUserManager; + private LdapConfiguration _ldapConfiguration; + + @Inject LdapUserManagerFactory _ldapUserManagerFactory; + + @Inject + LdapTrustMapDao _ldapTrustMapDao; + public LdapManagerImpl() { super(); } - public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, final LdapContextFactory ldapContextFactory, final LdapUserManager ldapUserManager) { + public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, final LdapContextFactory ldapContextFactory, final LdapUserManagerFactory ldapUserManagerFactory, + final LdapConfiguration ldapConfiguration) { super(); _ldapConfigurationDao = ldapConfigurationDao; _ldapContextFactory = ldapContextFactory; - _ldapUserManager = ldapUserManager; + _ldapUserManagerFactory = ldapUserManagerFactory; + _ldapConfiguration = ldapConfiguration; } @Override - public LdapConfigurationResponse addConfiguration(final String hostname, final int port) throws InvalidParameterValueException { - LdapConfigurationVO configuration = _ldapConfigurationDao.findByHostname(hostname); + public LdapConfigurationResponse addConfiguration(final LdapAddConfigurationCmd cmd) throws InvalidParameterValueException { + return addConfigurationInternal(cmd.getHostname(),cmd.getPort(),cmd.getDomainId()); + } + + @Override // TODO make private + public LdapConfigurationResponse addConfiguration(final String hostname, int port, final Long domainId) throws InvalidParameterValueException { + return addConfigurationInternal(hostname,port,domainId); + } + + private LdapConfigurationResponse addConfigurationInternal(final String hostname, int port, final Long domainId) throws InvalidParameterValueException { + // TODO evaluate what the right default should be + if(port <= 0) { + port = 389; + } + + // hostname:port is unique for domain binding + LdapConfigurationVO configuration = _ldapConfigurationDao.find(hostname, port, domainId); if (configuration == null) { + LdapContext context = null; try { final String providerUrl = "ldap://" + hostname + ":" + port; - _ldapContextFactory.createBindContext(providerUrl); - configuration = new LdapConfigurationVO(hostname, port); + context = _ldapContextFactory.createBindContext(providerUrl,domainId); + configuration = new LdapConfigurationVO(hostname, port, domainId); _ldapConfigurationDao.persist(configuration); - s_logger.info("Added new ldap server with hostname: " + hostname); - return new LdapConfigurationResponse(hostname, port); + s_logger.info("Added new ldap server with url: " + providerUrl + (domainId == null ? "": " for domain " + domainId)); + return createLdapConfigurationResponse(configuration); } catch (NamingException | IOException e) { s_logger.debug("NamingException while doing an LDAP bind", e); throw new InvalidParameterValueException("Unable to bind to the given LDAP server"); + } finally { + closeContext(context); } } else { throw new InvalidParameterValueException("Duplicate configuration"); } } + /** + * TODO decide if the principal is good enough to get the domain id or we need to add it as parameter + * @param principal + * @param password + * @param domainId + * @return + */ @Override - public boolean canAuthenticate(final String username, final String password) { - final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); + public boolean canAuthenticate(final String principal, final String password, final Long domainId) { try { - final LdapUser user = getUser(escapedUsername); - final String principal = user.getPrincipal(); - final LdapContext context = _ldapContextFactory.createUserContext(principal, password); + // TODO return the right account for this user + final LdapContext context = _ldapContextFactory.createUserContext(principal, password,domainId); closeContext(context); return true; - } catch (NamingException | IOException | NoLdapUserMatchingQueryException e) { - s_logger.debug("Exception while doing an LDAP bind for user "+" "+username, e); - s_logger.info("Failed to authenticate user: " + username + ". incorrect password."); + } catch (NamingException | IOException e) { + s_logger.debug("Exception while doing an LDAP bind for user "+" "+principal, e); + s_logger.info("Failed to authenticate user: " + principal + ". incorrect password."); return false; } } @@ -112,16 +157,17 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { context.close(); } } catch (final NamingException e) { - s_logger.warn(e.getMessage(),e); + s_logger.warn(e.getMessage(), e); } } @Override public LdapConfigurationResponse createLdapConfigurationResponse(final LdapConfigurationVO configuration) { - final LdapConfigurationResponse response = new LdapConfigurationResponse(); - response.setHostname(configuration.getHostname()); - response.setPort(configuration.getPort()); - return response; + String domainUuid = null; + if(configuration.getDomainId() != null) { + domainUuid = domainDao.findById(configuration.getDomainId()).getUuid(); + } + return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid); } @Override @@ -137,14 +183,23 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { } @Override - public LdapConfigurationResponse deleteConfiguration(final String hostname) throws InvalidParameterValueException { - final LdapConfigurationVO configuration = _ldapConfigurationDao.findByHostname(hostname); + public LdapConfigurationResponse deleteConfiguration(final LdapDeleteConfigurationCmd cmd) throws InvalidParameterValueException { + return deleteConfigurationInternal(cmd.getHostname(), cmd.getPort(), cmd.getDomainId()); + } + + @Override + public LdapConfigurationResponse deleteConfiguration(final String hostname, int port, Long domainId) throws InvalidParameterValueException { + return deleteConfigurationInternal(hostname, port, domainId); + } + + private LdapConfigurationResponse deleteConfigurationInternal(final String hostname, int port, Long domainId) throws InvalidParameterValueException { + final LdapConfigurationVO configuration = _ldapConfigurationDao.find(hostname,port,domainId); 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()); + s_logger.info("Removed ldap server with url: " + hostname + ':' + port + (domainId == null ? "" : " for domain id " + domainId)); + return createLdapConfigurationResponse(configuration); } } @@ -160,17 +215,19 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { cmdList.add(LdapImportUsersCmd.class); cmdList.add(LDAPConfigCmd.class); cmdList.add(LDAPRemoveCmd.class); + cmdList.add(LinkDomainToLdapCmd.class); + cmdList.add(LinkAccountToLdapCmd.class); return cmdList; } @Override - public LdapUser getUser(final String username) throws NoLdapUserMatchingQueryException { + public LdapUser getUser(final String username, Long domainId) throws NoLdapUserMatchingQueryException { LdapContext context = null; try { - context = _ldapContextFactory.createBindContext(); + context = _ldapContextFactory.createBindContext(domainId); final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); - return _ldapUserManager.getUser(escapedUsername, context); + return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId); } catch (NamingException | IOException e) { s_logger.debug("ldap Exception: ",e); @@ -181,11 +238,26 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { } @Override - public List getUsers() throws NoLdapUserMatchingQueryException { + public LdapUser getUser(final String username, final String type, final String name, Long domainId) throws NoLdapUserMatchingQueryException { LdapContext context = null; try { - context = _ldapContextFactory.createBindContext(); - return _ldapUserManager.getUsers(context); + context = _ldapContextFactory.createBindContext(domainId); + final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); + return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, type, name, context, domainId); + } catch (NamingException | IOException e) { + s_logger.debug("ldap Exception: ",e); + throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + " in group: " + name + " of type: " + type); + } finally { + closeContext(context); + } + } + + @Override + public List getUsers(Long domainId) throws NoLdapUserMatchingQueryException { + LdapContext context = null; + try { + context = _ldapContextFactory.createBindContext(domainId); + return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId); } catch (NamingException | IOException e) { s_logger.debug("ldap Exception: ",e); throw new NoLdapUserMatchingQueryException("*"); @@ -195,11 +267,11 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { } @Override - public List getUsersInGroup(String groupName) throws NoLdapUserMatchingQueryException { + public List getUsersInGroup(String groupName, Long domainId) throws NoLdapUserMatchingQueryException { LdapContext context = null; try { - context = _ldapContextFactory.createBindContext(); - return _ldapUserManager.getUsersInGroup(groupName, context); + context = _ldapContextFactory.createBindContext(domainId); + return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId); } catch (NamingException | IOException e) { s_logger.debug("ldap NamingException: ",e); throw new NoLdapUserMatchingQueryException("groupName=" + groupName); @@ -217,7 +289,8 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { 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); + final Long domainId = cmd.getDomainId(); + final Pair, Integer> result = _ldapConfigurationDao.searchConfigurations(hostname, port, domainId); return new Pair, Integer>(result.first(), result.second()); } @@ -225,9 +298,10 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { public List searchUsers(final String username) throws NoLdapUserMatchingQueryException { LdapContext context = null; try { - context = _ldapContextFactory.createBindContext(); + // TODO search users per domain (only?) + context = _ldapContextFactory.createBindContext(null); final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); - return _ldapUserManager.getUsers("*" + escapedUsername + "*", context); + return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null); } catch (NamingException | IOException e) { s_logger.debug("ldap Exception: ",e); throw new NoLdapUserMatchingQueryException(username); @@ -235,4 +309,77 @@ public class LdapManagerImpl implements LdapManager, LdapValidator { closeContext(context); } } + + @Override + public LinkDomainToLdapResponse linkDomainToLdap(LinkDomainToLdapCmd cmd) { + Validate.isTrue(_ldapConfiguration.getBaseDn(cmd.getDomainId()) == null, "can not configure an ldap server and an ldap group/ou to a domain"); + Validate.notEmpty(cmd.getLdapDomain(), "ldapDomain cannot be empty, please supply a GROUP or OU name"); + return linkDomainToLdap(cmd.getDomainId(),cmd.getType(),cmd.getLdapDomain(),cmd.getAccountType()); + } + + private LinkDomainToLdapResponse linkDomainToLdap(Long domainId, String type, String name, short accountType) { + Validate.notNull(type, "type cannot be null. It should either be GROUP or OU"); + Validate.notNull(domainId, "domainId cannot be null."); + Validate.notEmpty(name, "GROUP or OU name cannot be empty"); + //Account type should be 0 or 2. check the constants in com.cloud.user.Account + Validate.isTrue(accountType==0 || accountType==2, "accountype should be either 0(normal user) or 2(domain admin)"); + LinkType linkType = LdapManager.LinkType.valueOf(type.toUpperCase()); + LdapTrustMapVO vo = _ldapTrustMapDao.persist(new LdapTrustMapVO(domainId, linkType, name, accountType, 0)); + DomainVO domain = domainDao.findById(vo.getDomainId()); + String domainUuid = ""; + if (domain == null) { + s_logger.error("no domain in database for id " + vo.getDomainId()); + } else { + domainUuid = domain.getUuid(); + } + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainUuid, vo.getType().toString(), vo.getName(), vo.getAccountType()); + return response; + } + + @Override + public LdapTrustMapVO getDomainLinkedToLdap(long domainId){ + return _ldapTrustMapDao.findByDomainId(domainId); + } + + @Override + public List getDomainLinkage(long domainId){ + return _ldapTrustMapDao.searchByDomainId(domainId); + } + + public LdapTrustMapVO getAccountLinkedToLdap(long domainId, long accountId){ + return _ldapTrustMapDao.findByAccount(domainId, accountId); + } + + @Override + public LdapTrustMapVO getLinkedLdapGroup(long domainId, String group) { + return _ldapTrustMapDao.findGroupInDomain(domainId, group); + } + + @Override public LinkAccountToLdapResponse linkAccountToLdap(LinkAccountToLdapCmd cmd) { + Validate.notNull(_ldapConfiguration.getBaseDn(cmd.getDomainId()), "can not configure an ldap server and an ldap group/ou to a domain"); + Validate.notNull(cmd.getDomainId(), "domainId cannot be null."); + Validate.notEmpty(cmd.getAccountName(), "accountName cannot be empty."); + Validate.notEmpty(cmd.getLdapDomain(), "ldapDomain cannot be empty, please supply a GROUP or OU name"); + Validate.notNull(cmd.getType(), "type cannot be null. It should either be GROUP or OU"); + Validate.notEmpty(cmd.getLdapDomain(), "GROUP or OU name cannot be empty"); + + LinkType linkType = LdapManager.LinkType.valueOf(cmd.getType().toUpperCase()); + Account account = accountDao.findActiveAccount(cmd.getAccountName(),cmd.getDomainId()); + if (account == null) { + account = new AccountVO(cmd.getAccountName(), cmd.getDomainId(), null, cmd.getAccountType(), UUID.randomUUID().toString()); + accountDao.persist((AccountVO)account); + } + Long accountId = account.getAccountId(); + LdapTrustMapVO vo = _ldapTrustMapDao.persist(new LdapTrustMapVO(cmd.getDomainId(), linkType, cmd.getLdapDomain(), cmd.getAccountType(), accountId)); + DomainVO domain = domainDao.findById(vo.getDomainId()); + String domainUuid = ""; + if (domain == null) { + s_logger.error("no domain in database for id " + vo.getDomainId()); + } else { + domainUuid = domain.getUuid(); + } + + LinkAccountToLdapResponse response = new LinkAccountToLdapResponse(domainUuid, vo.getType().toString(), vo.getName(), vo.getAccountType(), account.getUuid(), cmd.getAccountName()); + return response; + } } diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapTrustMapVO.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapTrustMapVO.java new file mode 100644 index 00000000000..c402747b813 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapTrustMapVO.java @@ -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 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_trust_map") +public class LdapTrustMapVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "type") + private LdapManager.LinkType type; + + @Column(name = "name") + private String name; + + @Column(name = "domain_id") + private long domainId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "account_type") + private short accountType; + + + public LdapTrustMapVO() { + } + + public LdapTrustMapVO(long domainId, LdapManager.LinkType type, String name, short accountType, long accountId) { + this.domainId = domainId; + this.type = type; + this.name = name; + this.accountType = accountType; + this.accountId = accountId; + } + + @Override + public long getId() { + return id; + } + + public LdapManager.LinkType getType() { + return type; + } + + public String getName() { + return name; + } + + public long getDomainId() { + return domainId; + } + + public short getAccountType() { + return accountType; + } + + public long getAccountId() { + return accountId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LdapTrustMapVO that = (LdapTrustMapVO) o; + + if (domainId != that.domainId) { + return false; + } + if (accountId != that.accountId) { + return false; + } + if (accountType != that.accountType) { + return false; + } + if (type != that.type) { + return false; + } + return name.equals(that.name); + + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + (int) (domainId ^ (domainId >>> 32)); + result = 31 * result + (int) (accountId ^ (accountId >>> 32)); + result = 31 * result + (int) accountType; + return result; + } +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java index 0a998f2655a..ac5aca7bd26 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUser.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.ldap; +import java.util.ArrayList; +import java.util.List; + public class LdapUser implements Comparable { private final String email; private final String principal; @@ -23,14 +26,19 @@ public class LdapUser implements Comparable { private final String lastname; private final String username; private final String domain; + private final boolean disabled; + private List memberships; - public LdapUser(final String username, final String email, final String firstname, final String lastname, final String principal, String domain) { + public LdapUser(final String username, final String email, final String firstname, final String lastname, final String principal, String domain, boolean disabled, + List memberships) { this.username = username; this.email = email; this.firstname = firstname; this.lastname = lastname; this.principal = principal; this.domain = domain; + this.disabled = disabled; + this.memberships = memberships == null ? new ArrayList() : memberships; } @Override @@ -74,6 +82,14 @@ public class LdapUser implements Comparable { return domain; } + public boolean isDisabled() { + return disabled; + } + + public List getMemberships() { + return memberships; + } + @Override public int hashCode() { return getUsername().hashCode(); diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java index 654a601a476..fd18d47a05d 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManager.java @@ -17,211 +17,28 @@ package org.apache.cloudstack.ldap; import java.io.IOException; -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.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.Control; import javax.naming.ldap.LdapContext; -import javax.naming.ldap.PagedResultsControl; -import javax.naming.ldap.PagedResultsResponseControl; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; +public interface LdapUserManager { -public class LdapUserManager { - private static final Logger s_logger = Logger.getLogger(LdapUserManager.class.getName()); - - @Inject - private LdapConfiguration _ldapConfiguration; - - public LdapUserManager() { + enum Provider { + MICROSOFTAD, OPENLDAP; } - public LdapUserManager(final LdapConfiguration ldapConfiguration) { - _ldapConfiguration = ldapConfiguration; - } + LdapUser getUser(final String username, final LdapContext context, Long domainId) throws NamingException, IOException; - private LdapUser createUser(final SearchResult result) throws NamingException { - final Attributes attributes = result.getAttributes(); + LdapUser getUser(final String username, final String type, final String name, final LdapContext context, Long domainId) throws NamingException, IOException; - 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.getNameInNamespace(); + List getUsers(final LdapContext context, Long domainId) throws NamingException, IOException; - String domain = principal.replace("cn=" + LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getCommonNameAttribute()) + ",", ""); - domain = domain.replace("," + _ldapConfiguration.getBaseDn(), ""); - domain = domain.replace("ou=", ""); + List getUsers(final String username, final LdapContext context, Long domainId) throws NamingException, IOException; - return new LdapUser(username, email, firstname, lastname, principal, domain); - } + List getUsersInGroup(String groupName, LdapContext context, Long domainId) throws NamingException; - private String generateSearchFilter(final String username) { - final StringBuilder userObjectFilter = new StringBuilder(); - userObjectFilter.append("(objectClass="); - userObjectFilter.append(_ldapConfiguration.getUserObject()); - userObjectFilter.append(")"); + List searchUsers(final LdapContext context, Long domainId) throws NamingException, IOException; - 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(); - } - - private String generateGroupSearchFilter(final String groupName) { - final StringBuilder groupObjectFilter = new StringBuilder(); - groupObjectFilter.append("(objectClass="); - groupObjectFilter.append(_ldapConfiguration.getGroupObject()); - groupObjectFilter.append(")"); - - final StringBuilder groupNameFilter = new StringBuilder(); - groupNameFilter.append("("); - groupNameFilter.append(_ldapConfiguration.getCommonNameAttribute()); - groupNameFilter.append("="); - groupNameFilter.append((groupName == null ? "*" : groupName)); - groupNameFilter.append(")"); - - final StringBuilder result = new StringBuilder(); - result.append("(&"); - result.append(groupObjectFilter); - result.append(groupNameFilter); - result.append(")"); - - return result.toString(); - } - - public LdapUser getUser(final String username, final LdapContext context) throws NamingException, IOException { - List result = searchUsers(username, context); - if (result!= null && result.size() == 1) { - return result.get(0); - } else { - throw new NamingException("No user found for username " + username); - } - } - - public List getUsers(final LdapContext context) throws NamingException, IOException { - return getUsers(null, context); - } - - public List getUsers(final String username, final LdapContext context) throws NamingException, IOException { - List users = searchUsers(username, context); - - if (CollectionUtils.isNotEmpty(users)) { - Collections.sort(users); - } - return users; - } - - public List getUsersInGroup(String groupName, LdapContext context) throws NamingException { - String attributeName = _ldapConfiguration.getGroupUniqueMemeberAttribute(); - final SearchControls controls = new SearchControls(); - controls.setSearchScope(_ldapConfiguration.getScope()); - controls.setReturningAttributes(new String[] {attributeName}); - - NamingEnumeration result = context.search(_ldapConfiguration.getBaseDn(), generateGroupSearchFilter(groupName), controls); - - final List users = new ArrayList(); - //Expecting only one result which has all the users - if (result.hasMoreElements()) { - Attribute attribute = result.nextElement().getAttributes().get(attributeName); - NamingEnumeration values = attribute.getAll(); - - while (values.hasMoreElements()) { - String userdn = String.valueOf(values.nextElement()); - try{ - users.add(getUserForDn(userdn, context)); - } catch (NamingException e){ - s_logger.info("Userdn: " + userdn + " Not Found:: Exception message: " + e.getMessage()); - } - } - } - - Collections.sort(users); - - return users; - } - - private LdapUser getUserForDn(String userdn, LdapContext context) throws NamingException { - final SearchControls controls = new SearchControls(); - controls.setSearchScope(_ldapConfiguration.getScope()); - controls.setReturningAttributes(_ldapConfiguration.getReturnAttributes()); - - NamingEnumeration result = context.search(userdn, "(objectClass=" + _ldapConfiguration.getUserObject() + ")", controls); - if (result.hasMoreElements()) { - return createUser(result.nextElement()); - } else { - throw new NamingException("No user found for dn " + userdn); - } - } - - public List searchUsers(final LdapContext context) throws NamingException, IOException { - return searchUsers(null, context); - } - - public List searchUsers(final String username, final LdapContext context) throws NamingException, IOException { - - final SearchControls searchControls = new SearchControls(); - - searchControls.setSearchScope(_ldapConfiguration.getScope()); - searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes()); - - String basedn = _ldapConfiguration.getBaseDn(); - if (StringUtils.isBlank(basedn)) { - throw new IllegalArgumentException("ldap basedn is not configured"); - } - byte[] cookie = null; - int pageSize = _ldapConfiguration.getLdapPageSize(); - context.setRequestControls(new Control[]{new PagedResultsControl(pageSize, Control.NONCRITICAL)}); - final List users = new ArrayList(); - NamingEnumeration results; - do { - results = context.search(basedn, generateSearchFilter(username), searchControls); - while (results.hasMoreElements()) { - final SearchResult result = results.nextElement(); - users.add(createUser(result)); - } - Control[] contextControls = context.getResponseControls(); - if (contextControls != null) { - for (Control control : contextControls) { - if (control instanceof PagedResultsResponseControl) { - PagedResultsResponseControl prrc = (PagedResultsResponseControl) control; - cookie = prrc.getCookie(); - } - } - } else { - s_logger.info("No controls were sent from the ldap server"); - } - context.setRequestControls(new Control[] {new PagedResultsControl(pageSize, cookie, Control.CRITICAL)}); - } while (cookie != null); - - return users; - } -} \ No newline at end of file + List searchUsers(final String username, final LdapContext context, Long domainId) throws NamingException, IOException; +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManagerFactory.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManagerFactory.java new file mode 100644 index 00000000000..f796ce23b4e --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUserManagerFactory.java @@ -0,0 +1,64 @@ +/* + * 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 org.apache.log4j.Logger; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.util.HashMap; +import java.util.Map; + +public class LdapUserManagerFactory implements ApplicationContextAware { + + + public static final Logger s_logger = Logger.getLogger(LdapUserManagerFactory.class.getName()); + + private static Map ldapUserManagerMap = new HashMap<>(); + + private ApplicationContext applicationCtx; + + public LdapUserManager getInstance(LdapUserManager.Provider provider) { + LdapUserManager ldapUserManager; + if (provider == LdapUserManager.Provider.MICROSOFTAD) { + ldapUserManager = ldapUserManagerMap.get(LdapUserManager.Provider.MICROSOFTAD); + if (ldapUserManager == null) { + ldapUserManager = new ADLdapUserManagerImpl(); + applicationCtx.getAutowireCapableBeanFactory().autowireBeanProperties(ldapUserManager, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); + ldapUserManagerMap.put(LdapUserManager.Provider.MICROSOFTAD, ldapUserManager); + } + } else { + //defaults to openldap + ldapUserManager = ldapUserManagerMap.get(LdapUserManager.Provider.OPENLDAP); + if (ldapUserManager == null) { + ldapUserManager = new OpenLdapUserManagerImpl(); + applicationCtx.getAutowireCapableBeanFactory().autowireBeanProperties(ldapUserManager, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); + ldapUserManagerMap.put(LdapUserManager.Provider.OPENLDAP, ldapUserManager); + } + } + return ldapUserManager; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + applicationCtx = applicationContext; + } +} 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 index d54a6991def..da0859f77ca 100644 --- a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUtils.java +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/LdapUtils.java @@ -16,9 +16,12 @@ // under the License. package org.apache.cloudstack.ldap; +import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; +import java.util.ArrayList; +import java.util.List; public final class LdapUtils { public static String escapeLDAPSearchFilter(final String filter) { @@ -56,6 +59,18 @@ public final class LdapUtils { return null; } + public static List getAttributeValues(final Attributes attributes, final String attributeName) throws NamingException { + ArrayList memberships = new ArrayList<>(); + final Attribute attribute = attributes.get(attributeName); + if (attribute != null) { + NamingEnumeration values = attribute.getAll(); + while(values.hasMore()) { + memberships.add(String.valueOf(values.next())); + } + } + return memberships; + } + private LdapUtils() { } } \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java new file mode 100644 index 00000000000..cb3824a2ef0 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java @@ -0,0 +1,311 @@ +// 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.io.IOException; +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.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.PagedResultsControl; +import javax.naming.ldap.PagedResultsResponseControl; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +public class OpenLdapUserManagerImpl implements LdapUserManager { + private static final Logger s_logger = Logger.getLogger(OpenLdapUserManagerImpl.class.getName()); + + @Inject + protected LdapConfiguration _ldapConfiguration; + + public OpenLdapUserManagerImpl() { + } + + public OpenLdapUserManagerImpl(final LdapConfiguration ldapConfiguration) { + _ldapConfiguration = ldapConfiguration; + } + + protected LdapUser createUser(final SearchResult result, Long domainId) throws NamingException { + final Attributes attributes = result.getAttributes(); + + final String username = LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getUsernameAttribute(domainId)); + final String email = LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getEmailAttribute(domainId)); + final String firstname = LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getFirstnameAttribute(domainId)); + final String lastname = LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getLastnameAttribute(domainId)); + final String principal = result.getNameInNamespace(); + final List memberships = LdapUtils.getAttributeValues(attributes, _ldapConfiguration.getUserMemberOfAttribute(domainId)); + + String domain = principal.replace("cn=" + LdapUtils.getAttributeValue(attributes, _ldapConfiguration.getCommonNameAttribute()) + ",", ""); + domain = domain.replace("," + _ldapConfiguration.getBaseDn(domainId), ""); + domain = domain.replace("ou=", ""); + + boolean disabled = isUserDisabled(result); + + return new LdapUser(username, email, firstname, lastname, principal, domain, disabled, memberships); + } + + private String generateSearchFilter(final String username, Long domainId) { + final StringBuilder userObjectFilter = new StringBuilder(); + userObjectFilter.append("(objectClass="); + userObjectFilter.append(_ldapConfiguration.getUserObject(domainId)); + userObjectFilter.append(")"); + + final StringBuilder usernameFilter = new StringBuilder(); + usernameFilter.append("("); + usernameFilter.append(_ldapConfiguration.getUsernameAttribute(domainId)); + usernameFilter.append("="); + usernameFilter.append((username == null ? "*" : username)); + usernameFilter.append(")"); + + final StringBuilder memberOfFilter = new StringBuilder(); + if (_ldapConfiguration.getSearchGroupPrinciple(domainId) != null) { + if(s_logger.isDebugEnabled()) { + s_logger.debug("adding search filter for '" + _ldapConfiguration.getSearchGroupPrinciple(domainId) + + "', using " + _ldapConfiguration.getUserMemberOfAttribute(domainId)); + } + memberOfFilter.append("(" + _ldapConfiguration.getUserMemberOfAttribute(domainId) + "="); + memberOfFilter.append(_ldapConfiguration.getSearchGroupPrinciple(domainId)); + memberOfFilter.append(")"); + } + + final StringBuilder result = new StringBuilder(); + result.append("(&"); + result.append(userObjectFilter); + result.append(usernameFilter); + result.append(memberOfFilter); + result.append(")"); + + return result.toString(); + } + + private String generateGroupSearchFilter(final String groupName, Long domainId) { + final StringBuilder groupObjectFilter = new StringBuilder(); + groupObjectFilter.append("(objectClass="); + groupObjectFilter.append(_ldapConfiguration.getGroupObject(domainId)); + groupObjectFilter.append(")"); + + final StringBuilder groupNameFilter = new StringBuilder(); + groupNameFilter.append("("); + groupNameFilter.append(_ldapConfiguration.getCommonNameAttribute()); + groupNameFilter.append("="); + groupNameFilter.append((groupName == null ? "*" : groupName)); + groupNameFilter.append(")"); + + final StringBuilder result = new StringBuilder(); + result.append("(&"); + result.append(groupObjectFilter); + result.append(groupNameFilter); + result.append(")"); + + return result.toString(); + } + + @Override + public LdapUser getUser(final String username, final LdapContext context, Long domainId) throws NamingException, IOException { + List result = searchUsers(username, context, domainId); + if (result!= null && result.size() == 1) { + return result.get(0); + } else { + throw new NamingException("No user found for username " + username); + } + } + + @Override + public LdapUser getUser(final String username, final String type, final String name, final LdapContext context, Long domainId) throws NamingException, IOException { + String basedn; + if("OU".equals(type)) { + basedn = name; + } else { + basedn = _ldapConfiguration.getBaseDn(domainId); + } + + final StringBuilder userObjectFilter = new StringBuilder(); + userObjectFilter.append("(objectClass="); + userObjectFilter.append(_ldapConfiguration.getUserObject(domainId)); + userObjectFilter.append(")"); + + final StringBuilder usernameFilter = new StringBuilder(); + usernameFilter.append("("); + usernameFilter.append(_ldapConfiguration.getUsernameAttribute(domainId)); + usernameFilter.append("="); + usernameFilter.append((username == null ? "*" : username)); + usernameFilter.append(")"); + + final StringBuilder memberOfFilter = new StringBuilder(); + if ("GROUP".equals(type)) { + memberOfFilter.append("(").append(getMemberOfAttribute(domainId)).append("="); + memberOfFilter.append(name); + memberOfFilter.append(")"); + } + + final StringBuilder searchQuery = new StringBuilder(); + searchQuery.append("(&"); + searchQuery.append(userObjectFilter); + searchQuery.append(usernameFilter); + searchQuery.append(memberOfFilter); + searchQuery.append(")"); + + return searchUser(basedn, searchQuery.toString(), context, domainId); + } + + protected String getMemberOfAttribute(final Long domainId) { + return _ldapConfiguration.getUserMemberOfAttribute(domainId); + } + + @Override + public List getUsers(final LdapContext context, Long domainId) throws NamingException, IOException { + return getUsers(null, context, domainId); + } + + @Override + public List getUsers(final String username, final LdapContext context, Long domainId) throws NamingException, IOException { + List users = searchUsers(username, context, domainId); + + if (CollectionUtils.isNotEmpty(users)) { + Collections.sort(users); + } + return users; + } + + @Override + public List getUsersInGroup(String groupName, LdapContext context, Long domainId) throws NamingException { + String attributeName = _ldapConfiguration.getGroupUniqueMemberAttribute(domainId); + final SearchControls controls = new SearchControls(); + controls.setSearchScope(_ldapConfiguration.getScope()); + controls.setReturningAttributes(new String[] {attributeName}); + + NamingEnumeration result = context.search(_ldapConfiguration.getBaseDn(domainId), generateGroupSearchFilter(groupName, domainId), controls); + + final List users = new ArrayList(); + //Expecting only one result which has all the users + if (result.hasMoreElements()) { + Attribute attribute = result.nextElement().getAttributes().get(attributeName); + NamingEnumeration values = attribute.getAll(); + + while (values.hasMoreElements()) { + String userdn = String.valueOf(values.nextElement()); + try{ + users.add(getUserForDn(userdn, context, domainId)); + } catch (NamingException e){ + s_logger.info("Userdn: " + userdn + " Not Found:: Exception message: " + e.getMessage()); + } + } + } + + Collections.sort(users); + + return users; + } + + private LdapUser getUserForDn(String userdn, LdapContext context, Long domainId) throws NamingException { + final SearchControls controls = new SearchControls(); + controls.setSearchScope(_ldapConfiguration.getScope()); + controls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId)); + + NamingEnumeration result = context.search(userdn, "(objectClass=" + _ldapConfiguration.getUserObject(domainId) + ")", controls); + if (result.hasMoreElements()) { + return createUser(result.nextElement(), domainId); + } else { + throw new NamingException("No user found for dn " + userdn); + } + } + + @Override + public List searchUsers(final LdapContext context, Long domainId) throws NamingException, IOException { + return searchUsers(null, context, domainId); + } + + protected boolean isUserDisabled(SearchResult result) throws NamingException { + return false; + } + + public LdapUser searchUser(final String basedn, final String searchString, final LdapContext context, Long domainId) throws NamingException, IOException { + final SearchControls searchControls = new SearchControls(); + + searchControls.setSearchScope(_ldapConfiguration.getScope()); + searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId)); + + NamingEnumeration results = context.search(basedn, searchString, searchControls); + if(s_logger.isDebugEnabled()) { + s_logger.debug("searching user(s) with filter: \"" + searchString + "\""); + } + final List users = new ArrayList(); + while (results.hasMoreElements()) { + final SearchResult result = results.nextElement(); + users.add(createUser(result, domainId)); + } + + if (users.size() == 1) { + return users.get(0); + } else { + throw new NamingException("No user found for basedn " + basedn + " and searchString " + searchString); + } + } + + @Override + public List searchUsers(final String username, final LdapContext context, Long domainId) throws NamingException, IOException { + + final SearchControls searchControls = new SearchControls(); + + searchControls.setSearchScope(_ldapConfiguration.getScope()); + searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId)); + + String basedn = _ldapConfiguration.getBaseDn(domainId); + if (StringUtils.isBlank(basedn)) { + throw new IllegalArgumentException("ldap basedn is not configured"); + } + byte[] cookie = null; + int pageSize = _ldapConfiguration.getLdapPageSize(domainId); + context.setRequestControls(new Control[]{new PagedResultsControl(pageSize, Control.NONCRITICAL)}); + final List users = new ArrayList(); + NamingEnumeration results; + do { + results = context.search(basedn, generateSearchFilter(username, domainId), searchControls); + while (results.hasMoreElements()) { + final SearchResult result = results.nextElement(); + if (!isUserDisabled(result)) { + users.add(createUser(result, domainId)); + } + } + Control[] contextControls = context.getResponseControls(); + if (contextControls != null) { + for (Control control : contextControls) { + if (control instanceof PagedResultsResponseControl) { + PagedResultsResponseControl prrc = (PagedResultsResponseControl) control; + cookie = prrc.getCookie(); + } + } + } else { + s_logger.info("No controls were sent from the ldap server"); + } + context.setRequestControls(new Control[] {new PagedResultsControl(pageSize, cookie, Control.CRITICAL)}); + } while (cookie != null); + + return users; + } +} \ No newline at end of file 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 index a2d5e65248e..e99c78be9b7 100644 --- 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 @@ -23,8 +23,19 @@ import org.apache.cloudstack.ldap.LdapConfigurationVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; +/** + * TODO the domain value null now searches for that specifically and there is no way to search for all domains + */ public interface LdapConfigurationDao extends GenericDao { + /** + * @deprecated there might well be more then one ldap implementation on a host and or a double binding of several domains + * @param hostname + * @return + */ + @Deprecated LdapConfigurationVO findByHostname(String hostname); - Pair, Integer> searchConfigurations(String hostname, int port); + LdapConfigurationVO find(String hostname, int port, Long domainId); + + Pair, Integer> searchConfigurations(String hostname, int port, Long domainId); } \ 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 index 0f2a0150eba..88fff7c0fb8 100644 --- 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 @@ -34,7 +34,8 @@ import com.cloud.utils.db.SearchCriteria.Op; @Local(value = {LdapConfigurationDao.class}) public class LdapConfigurationDaoImpl extends GenericDaoBase implements LdapConfigurationDao { private final SearchBuilder hostnameSearch; - private final SearchBuilder listAllConfigurationsSearch; + private final SearchBuilder listGlobalConfigurationsSearch; + private final SearchBuilder listDomainConfigurationsSearch; public LdapConfigurationDaoImpl() { super(); @@ -42,10 +43,16 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase, Integer> searchConfigurations(final String hostname, final int port) { - final SearchCriteria sc = listAllConfigurationsSearch.create(); + public LdapConfigurationVO find(String hostname, int port, Long domainId) { + SearchCriteria sc = getSearchCriteria(hostname, port, domainId); + return findOneBy(sc); + } + + @Override + public Pair, Integer> searchConfigurations(final String hostname, final int port, final Long domainId) { + SearchCriteria sc = getSearchCriteria(hostname, port, domainId); + return searchAndCount(sc, null); + } + + private SearchCriteria getSearchCriteria(String hostname, int port, Long domainId) { + SearchCriteria sc; + if (domainId == null) { + sc = listDomainConfigurationsSearch.create(); + } else { + sc = listDomainConfigurationsSearch.create(); + sc.setParameters("domain_id", domainId); + } if (hostname != null) { sc.setParameters("hostname", hostname); } - return searchAndCount(sc, null); + if (port > 0) { + sc.setParameters("port", port); + } + return sc; } } \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java new file mode 100644 index 00000000000..c3d2f8aedf4 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.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 org.apache.cloudstack.ldap.LdapTrustMapVO; + +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface LdapTrustMapDao extends GenericDao { + LdapTrustMapVO findByDomainId(long domainId); + LdapTrustMapVO findByAccount(long domainId, Long accountId); + LdapTrustMapVO findGroupInDomain(long domainId, String group); + List searchByDomainId(long domainId); +} diff --git a/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java new file mode 100644 index 00000000000..0ecd3413d14 --- /dev/null +++ b/plugins/user-authenticators/ldap/src/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java @@ -0,0 +1,80 @@ +/* + * 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 com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.ldap.LdapTrustMapVO; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.GenericDaoBase; + +import java.util.List; + +@Component +public class LdapTrustMapDaoImpl extends GenericDaoBase implements LdapTrustMapDao { + private final SearchBuilder domainIdSearch; + private final SearchBuilder groupSearch; + + public LdapTrustMapDaoImpl() { + super(); + domainIdSearch = createSearchBuilder(); + domainIdSearch.and("domainId", domainIdSearch.entity().getDomainId(), SearchCriteria.Op.EQ); + domainIdSearch.and("account_id", domainIdSearch.entity().getAccountId(),SearchCriteria.Op.EQ); + domainIdSearch.done(); + groupSearch = createSearchBuilder(); + groupSearch.and("domainId", groupSearch.entity().getDomainId(), SearchCriteria.Op.EQ); + groupSearch.and("name", groupSearch.entity().getName(),SearchCriteria.Op.EQ); + groupSearch.done(); + } + + @Override + public LdapTrustMapVO findByDomainId(long domainId) { + final SearchCriteria sc = domainIdSearch.create(); + sc.setParameters("domainId", domainId); + sc.setParameters("account_id", 0); + return findOneBy(sc); + } + + @Override + public LdapTrustMapVO findByAccount(long domainId, Long accountId) { + final SearchCriteria sc = domainIdSearch.create(); + sc.setParameters("domainId", domainId); + sc.setParameters("account_id", accountId); + return findOneBy(sc); + } + + @Override + public LdapTrustMapVO findGroupInDomain(long domainId, String group){ + final SearchCriteria sc = groupSearch.create(); + sc.setParameters("domainId", domainId); + sc.setParameters("name", group); + return findOneBy(sc); + + } + + @Override + public List searchByDomainId(long domainId) { + final SearchCriteria sc = domainIdSearch.create(); + sc.setParameters("domainId", domainId); + return search(sc,null); + } + +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/ADLdapUserManagerImplSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/ADLdapUserManagerImplSpec.groovy new file mode 100644 index 00000000000..4b631b44e3b --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/ADLdapUserManagerImplSpec.groovy @@ -0,0 +1,85 @@ +/* + * 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.ADLdapUserManagerImpl +import org.apache.cloudstack.ldap.LdapConfiguration +import spock.lang.Shared + +import javax.naming.directory.SearchControls +import javax.naming.ldap.LdapContext + +class ADLdapUserManagerImplSpec extends spock.lang.Specification { + + @Shared + ADLdapUserManagerImpl adLdapUserManager; + + @Shared + LdapConfiguration ldapConfiguration; + + def setup() { + adLdapUserManager = new ADLdapUserManagerImpl(); + ldapConfiguration = Mock(LdapConfiguration); + adLdapUserManager._ldapConfiguration = ldapConfiguration; + } + + def "test generate AD search filter with nested groups enabled"() { + ldapConfiguration.getUserObject() >> "user" + ldapConfiguration.getCommonNameAttribute() >> "CN" + ldapConfiguration.getBaseDn() >> "DC=cloud,DC=citrix,DC=com" + ldapConfiguration.isNestedGroupsEnabled() >> true + + def result = adLdapUserManager.generateADGroupSearchFilter(group); + expect: + assert result.contains("memberOf:1.2.840.113556.1.4.1941:=") + result == "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=" + group + ",DC=cloud,DC=citrix,DC=com))" + where: + group << ["dev", "dev-hyd"] + } + + def "test generate AD search filter with nested groups disabled"() { + ldapConfiguration.getUserObject() >> "user" + ldapConfiguration.getCommonNameAttribute() >> "CN" + ldapConfiguration.getBaseDn() >> "DC=cloud,DC=citrix,DC=com" + ldapConfiguration.isNestedGroupsEnabled() >> false + + def result = adLdapUserManager.generateADGroupSearchFilter(group); + expect: + assert result.contains("memberOf=") + result == "(&(objectClass=user)(memberOf=CN=" + group + ",DC=cloud,DC=citrix,DC=com))" + where: + group << ["dev", "dev-hyd"] + } + + def "test getUsersInGroup null group"() { + ldapConfiguration.getScope() >> SearchControls.SUBTREE_SCOPE + ldapConfiguration.getReturnAttributes(null) >> ["username", "firstname", "lastname", "email"] + ldapConfiguration.getBaseDn(null) >>> [null, null, "DC=cloud,DC=citrix,DC=com"] + + LdapContext context = Mock(LdapContext); + + when: + def result = adLdapUserManager.getUsersInGroup(group, context,null) + then: + thrown(IllegalArgumentException) + where: + group << [null, "group", null] + + } +} 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 index 51f8e84559a..96eb435f655 100644 --- 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 @@ -16,12 +16,16 @@ // under the License. package groovy.org.apache.cloudstack.ldap +import com.cloud.server.auth.UserAuthenticator +import com.cloud.user.AccountManager +import com.cloud.user.UserAccount 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 +import org.apache.cloudstack.ldap.LdapTrustMapVO +import org.apache.cloudstack.ldap.LdapUser class LdapAuthenticatorSpec extends spock.lang.Specification { @@ -38,10 +42,13 @@ class LdapAuthenticatorSpec extends spock.lang.Specification { } 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 + given: "We have an LdapManager, LdapConfiguration, userAccountDao and LdapAuthenticator" + def ldapManager = Mock(LdapManager) + def ldapUser = Mock(LdapUser) + ldapUser.isDisabled() >> false + ldapManager.isLdapEnabled() >> true + ldapManager.getUser("rmurphy", null) >> ldapUser + ldapManager.canAuthenticate(_, _, _) >> false UserAccountDao userAccountDao = Mock(UserAccountDao) userAccountDao.getUserAccount(_, _) >> new UserAccountVO() @@ -69,11 +76,14 @@ class LdapAuthenticatorSpec extends spock.lang.Specification { result.first() == false } - def "Test successful authentication"() { - given: "We have an LdapManager, LdapConfiguration, userAccountDao and LdapAuthenticator" - def ldapManager = Mock(LdapManager) - ldapManager.isLdapEnabled() >> true - ldapManager.canAuthenticate(_, _) >> true + def "Test successful authentication"() { + given: "We have an LdapManager, LdapConfiguration, userAccountDao and LdapAuthenticator" + def ldapManager = Mock(LdapManager) + def ldapUser = Mock(LdapUser) + ldapUser.isDisabled() >> false + ldapManager.isLdapEnabled() >> true + ldapManager.canAuthenticate(_, _, _) >> true + ldapManager.getUser("rmurphy", null) >> ldapUser UserAccountDao userAccountDao = Mock(UserAccountDao) userAccountDao.getUserAccount(_, _) >> new UserAccountVO() @@ -87,13 +97,151 @@ class LdapAuthenticatorSpec extends spock.lang.Specification { } 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" + 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" + } + + def "test authentication when ldap is disabled"(){ + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + def ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao) + ldapManager.isLdapEnabled() >> false + + when: + Pair result = ldapAuthenticator.authenticate("rajanik", "password", 1, null) + then: + result.first() == false + result.second() == null + + } + + // tests when domain is linked to LDAP + def "test authentication when domain is linked and user disabled in ldap"(){ + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + AccountManager accountManager = Mock(AccountManager) + + def ldapAuthenticator = new LdapAuthenticator() + ldapAuthenticator._ldapManager = ldapManager + ldapAuthenticator._userAccountDao = userAccountDao + ldapAuthenticator._accountManager = accountManager + + long domainId = 1; + String username = "rajanik" + LdapManager.LinkType type = LdapManager.LinkType.GROUP + String name = "CN=test,DC=ccp,DC=citrix,DC=com" + + ldapManager.isLdapEnabled() >> true + UserAccount userAccount = Mock(UserAccount) + userAccountDao.getUserAccount(username, domainId) >> userAccount + userAccount.getId() >> 1 + ldapManager.getDomainLinkedToLdap(domainId) >> new LdapTrustMapVO(domainId, type, name, (short)2) + ldapManager.getUser(username, type.toString(), name) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", true, null) + //user should be disabled in cloudstack + accountManager.disableUser(1) >> userAccount + + when: + Pair result = ldapAuthenticator.authenticate(username, "password", domainId, null) + then: + result.first() == false + result.second() == UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT + } + + def "test authentication when domain is linked and first time user can authenticate in ldap"(){ + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + AccountManager accountManager = Mock(AccountManager) + + def ldapAuthenticator = new LdapAuthenticator() + ldapAuthenticator._ldapManager = ldapManager + ldapAuthenticator._userAccountDao = userAccountDao + ldapAuthenticator._accountManager = accountManager + + long domainId = 1; + String username = "rajanik" + LdapManager.LinkType type = LdapManager.LinkType.GROUP + String name = "CN=test,DC=ccp,DC=citrix,DC=com" + + ldapManager.isLdapEnabled() >> true + userAccountDao.getUserAccount(username, domainId) >> null + ldapManager.getDomainLinkedToLdap(domainId) >> new LdapTrustMapVO(domainId, type, name, (short)0) + ldapManager.getUser(username, type.toString(), name) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", false, null) + ldapManager.canAuthenticate(_, _, _) >> true + //user should be created in cloudstack + accountManager.createUserAccount(username, "", "firstname", "lastname", "email", null, username, (short) 2, domainId, username, null, _, _, User.Source.LDAP) >> Mock(UserAccount) + + when: + Pair result = ldapAuthenticator.authenticate(username, "password", domainId, null) + then: + result.first() == true + result.second() == null + } + + def "test authentication when domain is linked and existing user can authenticate in ldap"(){ + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + AccountManager accountManager = Mock(AccountManager) + + def ldapAuthenticator = new LdapAuthenticator() + ldapAuthenticator._ldapManager = ldapManager + ldapAuthenticator._userAccountDao = userAccountDao + ldapAuthenticator._accountManager = accountManager + + long domainId = 1; + String username = "rajanik" + LdapManager.LinkType type = LdapManager.LinkType.GROUP + String name = "CN=test,DC=ccp,DC=citrix,DC=com" + + ldapManager.isLdapEnabled() >> true + UserAccount userAccount = Mock(UserAccount) + userAccountDao.getUserAccount(username, domainId) >> userAccount + userAccount.getId() >> 1 + userAccount.getState() >> Account.State.disabled.toString() + ldapManager.getDomainLinkedToLdap(domainId) >> new LdapTrustMapVO(domainId, type, name, (short)2) + ldapManager.getUser(username, type.toString(), name) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", false, null) + ldapManager.canAuthenticate(_, _, _) >> true + //user should be enabled in cloudstack if disabled + accountManager.enableUser(1) >> userAccount + + when: + Pair result = ldapAuthenticator.authenticate(username, "password", domainId, null) + then: + result.first() == true + result.second() == null + } + + def "test authentication when domain is linked and user cannot authenticate in ldap"(){ + LdapManager ldapManager = Mock(LdapManager) + UserAccountDao userAccountDao = Mock(UserAccountDao) + AccountManager accountManager = Mock(AccountManager) + + def ldapAuthenticator = new LdapAuthenticator() + ldapAuthenticator._ldapManager = ldapManager + ldapAuthenticator._userAccountDao = userAccountDao + ldapAuthenticator._accountManager = accountManager + + long domainId = 1; + String username = "rajanik" + LdapManager.LinkType type = LdapManager.LinkType.GROUP + String name = "CN=test,DC=ccp,DC=citrix,DC=com" + + ldapManager.isLdapEnabled() >> true + UserAccount userAccount = Mock(UserAccount) + userAccountDao.getUserAccount(username, domainId) >> userAccount + ldapManager.getDomainLinkedToLdap(domainId) >> new LdapTrustMapVO(domainId, type, name, (short)2) + ldapManager.getUser(username, type.toString(), name) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", false, null) + ldapManager.canAuthenticate(_, _, _) >> false + + when: + Pair result = ldapAuthenticator.authenticate(username, "password", domainId, null) + then: + result.first() == false + result.second() == UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT } } 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 index 144890957f2..e94b0d40fb4 100644 --- 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 @@ -22,8 +22,8 @@ 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" + expect: "that hostnameSearch and listDomainConfigurationsSearch is configured" ldapConfigurationDaoImpl.hostnameSearch != null; - ldapConfigurationDaoImpl.listAllConfigurationsSearch != null + ldapConfigurationDaoImpl.listDomainConfigurationsSearch != null } } diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy index adc3463afde..dab18105d7e 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy @@ -19,53 +19,26 @@ package groovy.org.apache.cloudstack.ldap import org.apache.cloudstack.framework.config.ConfigKey import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import com.cloud.utils.Pair -import org.apache.cloudstack.api.ServerApiException import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl import org.apache.cloudstack.framework.config.impl.ConfigurationVO import org.apache.cloudstack.ldap.LdapConfiguration import org.apache.cloudstack.ldap.LdapConfigurationVO -import org.apache.cloudstack.ldap.LdapManager -import org.apache.cxf.common.util.StringUtils +import org.apache.cloudstack.ldap.LdapUserManager +import org.apache.cloudstack.ldap.dao.LdapConfigurationDao 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) + def ldapConfigurationDao = Mock(LdapConfigurationDao) + def ldapConfiguration = new LdapConfiguration(ldapConfigurationDao) 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) @@ -176,87 +149,12 @@ class LdapConfigurationSpec extends spock.lang.Specification { LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManager) when: "A request is made to get the providerUrl" - String providerUrl = ldapConfiguration.getProviderUrl() + 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 - } - - def "Test getgroupobject"() { - given: "We have configdao for ldap group object" - def configDao = Mock(ConfigurationDao) - configDao.getValue("ldap.group.object") >> groupObject - - def ldapManger = Mock(LdapManager) - LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManger) - def expectedResult = groupObject == null ? "groupOfUniqueNames" : groupObject - - def result = ldapConfiguration.getGroupObject() - expect: - result == expectedResult - where: - groupObject << [null, "", "groupOfUniqueNames"] - } - - def "Test getGroupUniqueMemeberAttribute"() { - given: "We have configdao for ldap group object" - def configDao = Mock(ConfigurationDao) - configDao.getValue("ldap.group.user.uniquemember") >> groupObject - - def ldapManger = Mock(LdapManager) - LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapManger) - def expectedResult = groupObject == null ? "uniquemember" : groupObject - - def result = ldapConfiguration.getGroupUniqueMemeberAttribute() - expect: - result == expectedResult - where: - groupObject << [null, "", "uniquemember"] - } - def "Test getReadTimeout"() { given: "We have configdao for ldap group object" def configDao = Mock(ConfigurationDao) @@ -273,11 +171,34 @@ class LdapConfigurationSpec extends spock.lang.Specification { def expected = timeout == null ? 1000 : timeout.toLong() //1000 is the default value - def result = ldapConfiguration.getReadTimeout() + def result = ldapConfiguration.getReadTimeout(null) expect: result == expected where: timeout << ["1000000", "1000", null] } + def "Test getLdapProvider()"() { + given: "We have configdao for ldap group object" + def configDao = Mock(ConfigurationDao) + ConfigurationVO configurationVo = new ConfigurationVO("ldap.read.timeout", LdapConfiguration.ldapProvider); + configurationVo.setValue(provider) + configDao.findById("ldap.provider") >> configurationVo + + def configDepotImpl = Mock(ConfigDepotImpl) + configDepotImpl.global() >> configDao + ConfigKey.init(configDepotImpl) + + def ldapConfigurationDao = Mock(LdapConfigurationDao) + LdapConfiguration ldapConfiguration = new LdapConfiguration(configDao, ldapConfigurationDao) + + def expected = provider.equalsIgnoreCase("microsoftad") ? LdapUserManager.Provider.MICROSOFTAD : LdapUserManager.Provider.OPENLDAP //"openldap" is the default value + + def result = ldapConfiguration.getLdapProvider(null) + expect: + println "asserting for provider configuration: " + provider + result == expected + where: + provider << ["openldap", "microsoftad", "", " ", "xyz", "MicrosoftAd", "OpenLdap", "MicrosoftAD"] + } } 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 index 2a665283edd..05d4aa1945f 100644 --- 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 @@ -22,7 +22,6 @@ 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 @@ -41,7 +40,7 @@ class LdapContextFactorySpec extends spock.lang.Specification { ldapConfiguration = Mock(LdapConfiguration) ldapConfiguration.getFactory() >> "com.sun.jndi.ldap.LdapCtxFactory" - ldapConfiguration.getProviderUrl() >> "ldap://localhost:389" + ldapConfiguration.getProviderUrl(_) >> "ldap://localhost:389" ldapConfiguration.getAuthentication() >> "none" ldapConfiguration.getScope() >> SearchControls.SUBTREE_SCOPE ldapConfiguration.getReturnAttributes() >> ["uid", "mail", "cn"] @@ -49,11 +48,11 @@ class LdapContextFactorySpec extends spock.lang.Specification { ldapConfiguration.getEmailAttribute() >> "mail" ldapConfiguration.getFirstnameAttribute() >> "givenname" ldapConfiguration.getLastnameAttribute() >> "sn" - ldapConfiguration.getBaseDn() >> "dc=cloudstack,dc=org" + ldapConfiguration.getBaseDn(_) >> "dc=cloudstack,dc=org" ldapConfiguration.getSSLStatus() >> true ldapConfiguration.getTrustStore() >> "/tmp/ldap.ts" ldapConfiguration.getTrustStorePassword() >> "password" - ldapConfiguration.getReadTimeout() >> 1000 + ldapConfiguration.getReadTimeout(_) >> 1000 ldapConfiguration.getLdapPageSize() >> 1 username = "rmurphy" @@ -87,7 +86,7 @@ class LdapContextFactorySpec extends spock.lang.Specification { 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.provider.url'] == ldapConfiguration.getProviderUrl(null) result['java.naming.factory.initial'] == ldapConfiguration.getFactory() result['java.naming.security.principal'] == null result['java.naming.security.authentication'] == ldapConfiguration.getAuthentication() @@ -102,7 +101,7 @@ class LdapContextFactorySpec extends spock.lang.Specification { 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.provider.url'] == ldapConfiguration.getProviderUrl(null) result['java.naming.factory.initial'] == ldapConfiguration.getFactory() result['java.naming.security.principal'] == principal result['java.naming.security.authentication'] == "simple" diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy index 5805e0b6591..9fe64aec786 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy @@ -16,54 +16,13 @@ // 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 org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException; - -import javax.naming.NamingException +import org.apache.cloudstack.api.command.LdapCreateAccountCmd +import com.cloud.user.AccountService 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 NoLdapUserMatchingQueryException() } - 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,ou=engineering,dc=cloudstack,dc=org", "engineering") - 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) @@ -100,48 +59,48 @@ class LdapCreateAccountCmdSpec extends spock.lang.Specification { } 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","domain")) - then: "the result is true" - result == true - } + 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", "domain", false, null)) + 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","domain")) - then: "An exception is thrown" - thrown Exception - } + 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", "domain", false, null)) + 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 - } + 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", false)) + 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","domain")) - then: "An exception is thown" - 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", "domain", false, null)) + then: "An exception is thown" + thrown Exception + } def "Test validation of a user"() { given: "We have an LdapManager, AccountService andL dapCreateAccount" 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 index 31d56ef68cb..caa524701b2 100644 --- 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 @@ -27,7 +27,7 @@ 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() } + ldapManager.deleteConfiguration(_, 0, null) >> { throw new InvalidParameterValueException() } def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) when:"LdapDeleteConfigurationCmd is executed and no configuration exists" ldapDeleteConfigurationCmd.execute() @@ -48,7 +48,7 @@ class LdapDeleteConfigurationCmdSpec extends spock.lang.Specification { def "Test successful response from execute"() { given: "We have an LdapManager and LdapDeleteConfigurationCmd" def ldapManager = Mock(LdapManager) - ldapManager.deleteConfiguration(_) >> new LdapConfigurationResponse("localhost") + ldapManager.deleteConfiguration(_, 0, null) >> new LdapConfigurationResponse("localhost") def ldapDeleteConfigurationCmd = new LdapDeleteConfigurationCmd(ldapManager) when: "LdapDeleteConfigurationCmd is executed" ldapDeleteConfigurationCmd.execute() diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy index 3c9b584d7e5..68b910811c7 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy @@ -24,10 +24,8 @@ import com.cloud.user.DomainService import com.cloud.user.User import com.cloud.user.UserAccountVO import com.cloud.user.UserVO -import org.apache.cloudstack.api.command.LdapCreateAccountCmd import org.apache.cloudstack.api.command.LdapImportUsersCmd import org.apache.cloudstack.api.response.LdapUserResponse -import org.apache.cloudstack.context.CallContext import org.apache.cloudstack.ldap.LdapManager import org.apache.cloudstack.ldap.LdapUser @@ -53,9 +51,9 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { def accountService = Mock(AccountService) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> [response1, response2] @@ -81,9 +79,9 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { def accountService = Mock(AccountService) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsersInGroup("TestGroup") >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsersInGroup("TestGroup", null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> [response1, response2] @@ -110,9 +108,9 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { def accountService = Mock(AccountService) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsersInGroup("TestGroup") >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsersInGroup("TestGroup", null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> [response1, response2] @@ -139,9 +137,9 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { def accountService = Mock(AccountService) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> [response1, response2] @@ -169,8 +167,8 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { ldapImportUsersCmd.domainId = varDomainId ldapImportUsersCmd.groupName = varGroupName - def ldapUser1 = new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") - def ldapUser2 = new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering"); + def ldapUser1 = new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null) + def ldapUser2 = new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null); Domain domain = new DomainVO(expectedDomainName, 1L, 1L, expectedDomainName, UUID.randomUUID().toString()); if (varDomainId != null) { @@ -204,8 +202,8 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" def ldapManager = Mock(LdapManager) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> response1 @@ -234,8 +232,8 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" def ldapManager = Mock(LdapManager) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> response1 @@ -263,8 +261,8 @@ class LdapImportUsersCmdSpec extends spock.lang.Specification { given: "We have an LdapManager, DomainService, two users and a LdapImportUsersCmd" def ldapManager = Mock(LdapManager) List users = new ArrayList() - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering") ldapManager.createLdapUserResponse(_) >>> response1 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 index 4b32eb1ecd6..d6410d96866 100644 --- 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 @@ -40,7 +40,7 @@ class LdapListUsersCmdSpec extends spock.lang.Specification { 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()} + ldapManager.getUsers(null) >> {throw new NoLdapUserMatchingQueryException()} def queryService = Mock(QueryService) def ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService) when: "LdapListUsersCmd is executed" @@ -53,8 +53,8 @@ class LdapListUsersCmdSpec extends spock.lang.Specification { 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", null)) - ldapManager.getUsers() >> users + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null)) + ldapManager.getUsers(null) >> users LdapUserResponse response = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) ldapManager.createLdapUserResponse(_) >> response def queryService = Mock(QueryService) @@ -92,7 +92,7 @@ class LdapListUsersCmdSpec extends spock.lang.Specification { queryService.searchForUsers(_) >> queryServiceResponse - def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) + def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null) def ldapListUsersCmd = new LdapListUsersCmd(ldapManager,queryService) when: "isACloudstackUser is executed" @@ -109,7 +109,7 @@ class LdapListUsersCmdSpec extends spock.lang.Specification { queryService.searchForUsers(_) >> new ListResponse() - def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) + def ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null) def ldapListUsersCmd = new LdapListUsersCmd(ldapManager,queryService) when: "isACloudstackUser is executed" diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy index ee317206854..fd047931acb 100644 --- a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy @@ -16,24 +16,17 @@ // under the License. package groovy.org.apache.cloudstack.ldap -import org.apache.cloudstack.api.command.LDAPConfigCmd -import org.apache.cloudstack.api.command.LDAPRemoveCmd -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.LdapImportUsersCmd -import org.apache.cloudstack.api.command.LdapListUsersCmd -import org.apache.cloudstack.api.command.LdapUserSearchCmd +import com.cloud.exception.InvalidParameterValueException +import com.cloud.utils.Pair +import org.apache.cloudstack.api.command.LdapListConfigurationCmd +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse +import org.apache.cloudstack.ldap.* +import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl +import org.apache.cloudstack.ldap.dao.LdapTrustMapDao 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 +import javax.naming.ldap.LdapContext class LdapManagerImplSpec extends spock.lang.Specification { def "Test failing of getUser due to bind issue"() { @@ -44,7 +37,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { ldapContextFactory.createBindContext() >> { throw new NoLdapUserMatchingQueryException() } def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) when: "We search for a user but there is a bind issue" - ldapManager.getUser("rmurphy") + ldapManager.getUser("rmurphy", null) then: "an exception is thrown" thrown NoLdapUserMatchingQueryException } @@ -57,7 +50,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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() + ldapManager.getUsers(null) then: "An exception is thrown" thrown NoLdapUserMatchingQueryException } @@ -96,7 +89,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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,ou=engineering,dc=cloudstack,dc=org", - "engineering")) + "engineering", false, null)) then: "The result of the response should match the given ldap user" result.username == "rmurphy" result.email == "rmurphy@test.com" @@ -113,11 +106,11 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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", null)) + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null)) ldapUserManager.getUsers(_) >> users; def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) when: "We search for a group of users" - def result = ldapManager.getUsers() + def result = ldapManager.getUsers(null) then: "A list greater than 0 is returned" result.size() > 0; } @@ -128,10 +121,10 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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", null) - def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) + ldapUserManager.getUser(_, _, _) >> new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null) + def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManagerFactory, ldapConfiguration) when: "We search for a user" - def result = ldapManager.getUser("rmurphy") + def result = ldapManager.getUser("rmurphy", null) then: "The user is returned" result.username == "rmurphy" result.email == "rmurphy@test.com" @@ -159,10 +152,13 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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", null) } + def ldapUserManagerFactory = Mock(LdapUserManagerFactory) + ldapUserManagerFactory.getInstance(_) >> ldapUserManager + def ldapConfiguration = Mock(LdapConfiguration) + def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManagerFactory, ldapConfiguration]) + ldapManager.getUser(_, null) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) } when: "The user attempts to authenticate with a bad password" - def result = ldapManager.canAuthenticate("rmurphy", "password") + def result = ldapManager.canAuthenticate("rmurphy", "password", null) then: "The authentication fails" result == false } @@ -188,7 +184,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { ldapConfigurationDao.findByHostname(_) >> null def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) when: "A ldap configuration that doesn't exist is deleted" - ldapManager.deleteConfiguration("localhost") + ldapManager.deleteConfiguration("localhost", 0, null) then: "A exception is thrown" thrown InvalidParameterValueException } @@ -213,10 +209,13 @@ class LdapManagerImplSpec extends spock.lang.Specification { 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", null) } + def ldapUserManagerFactory = Mock(LdapUserManagerFactory) + def ldapConfiguration = Mock(LdapConfiguration) + ldapUserManagerFactory.getInstance(_) >> ldapUserManager + def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManagerFactory, ldapConfiguration]) + ldapManager.getUser(_, null) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) } when: "A user authenticates" - def result = ldapManager.canAuthenticate("rmurphy", "password") + def result = ldapManager.canAuthenticate("rmurphy", "password", null) then: "The result is true" result == true } @@ -234,7 +233,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { ldapConfigurationDao.remove(_) >> null def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) when: "A ldap configuration is deleted" - def result = ldapManager.deleteConfiguration("localhost") + def result = ldapManager.deleteConfiguration("localhost", 0, null) then: "The deleted configuration is returned" result.hostname == "localhost" result.port == 389 @@ -248,7 +247,7 @@ class LdapManagerImplSpec extends spock.lang.Specification { ldapContextFactory.createBindContext() >> null; List users = new ArrayList(); - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering")) + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)) ldapUserManager.getUsers(_, _) >> users; def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) @@ -368,12 +367,162 @@ class LdapManagerImplSpec extends spock.lang.Specification { def ldapUserManager = Mock(LdapUserManager) ldapContextFactory.createBindContext() >> null List users = new ArrayList<>(); - users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", "engineering")) + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", "engineering", false, null)) ldapUserManager.getUsersInGroup("engineering", _) >> users; def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager) when: "We search for a group of users" - def result = ldapManager.getUsersInGroup("engineering") + def result = ldapManager.getUsersInGroup("engineering", null) then: "A list greater of size one is returned" result.size() == 1; } + + def "test linkDomainToLdap invalid ldap group type"() { + def ldapManager = new LdapManagerImpl() + LdapTrustMapDao ldapTrustMapDao = Mock(LdapTrustMapDao) + ldapManager._ldapTrustMapDao = ldapTrustMapDao + + def domainId = 1 + when: + println("using type: " + type) + LinkDomainToLdapResponse response = ldapManager.linkDomainToLdap(domainId, type, "CN=test,DC=CCP,DC=Citrix,DC=Com", (short)2) + then: + thrown(IllegalArgumentException) + where: + type << ["", null, "TEST", "TEST TEST"] + } + def "test linkDomainToLdap invalid domain"() { + def ldapManager = new LdapManagerImpl() + LdapTrustMapDao ldapTrustMapDao = Mock(LdapTrustMapDao) + ldapManager._ldapTrustMapDao = ldapTrustMapDao + + when: + LinkDomainToLdapResponse response = ldapManager.linkDomainToLdap(null, "GROUP", "CN=test,DC=CCP,DC=Citrix,DC=Com", (short)2) + then: + thrown(IllegalArgumentException) + } + def "test linkDomainToLdap invalid ldap name"() { + def ldapManager = new LdapManagerImpl() + LdapTrustMapDao ldapTrustMapDao = Mock(LdapTrustMapDao) + ldapManager._ldapTrustMapDao = ldapTrustMapDao + + def domainId = 1 + when: + println("using name: " + name) + LinkDomainToLdapResponse response = ldapManager.linkDomainToLdap(domainId, "GROUP", name, (short)2) + then: + thrown(IllegalArgumentException) + where: + name << ["", null] + } + def "test linkDomainToLdap invalid accountType"(){ + + def ldapManager = new LdapManagerImpl() + LdapTrustMapDao ldapTrustMapDao = Mock(LdapTrustMapDao) + ldapManager._ldapTrustMapDao = ldapTrustMapDao + + def domainId = 1 + when: + println("using accountType: " + accountType) + LinkDomainToLdapResponse response = ldapManager.linkDomainToLdap(domainId, "GROUP", "TEST", (short)accountType) + then: + thrown(IllegalArgumentException) + where: + accountType << [-1, 1, 3, 4, 5, 6, 20000, -500000] + } + def "test linkDomainToLdap when all is well"(){ + def ldapManager = new LdapManagerImpl() + LdapTrustMapDao ldapTrustMapDao = Mock(LdapTrustMapDao) + ldapManager._ldapTrustMapDao = ldapTrustMapDao + + def domainId=1 + def type=LdapManager.LinkType.GROUP + def name="CN=test,DC=CCP, DC=citrix,DC=com" + short accountType=2 + + 1 * ldapTrustMapDao.persist(new LdapTrustMapVO(domainId, type, name, accountType)) >> new LdapTrustMapVO(domainId, type, name, accountType) + + when: + LinkDomainToLdapResponse response = ldapManager.linkDomainToLdap(domainId, type.toString(), name, accountType) + then: + response.getDomainId() == domainId + response.getType() == type.toString() + response.getName() == name + response.getAccountType() == accountType + } + + def "test getUser(username,type,group) when username disabled in ldap"(){ + def ldapUserManager = Mock(LdapUserManager) + def ldapUserManagerFactory = Mock(LdapUserManagerFactory) + ldapUserManagerFactory.getInstance(_) >> ldapUserManager + def ldapContextFactory = Mock(LdapContextFactory) + ldapContextFactory.createBindContext() >> Mock(LdapContext) + def ldapConfiguration = Mock(LdapConfiguration) + + def ldapManager = new LdapManagerImpl() + ldapManager._ldapUserManagerFactory = ldapUserManagerFactory + ldapManager._ldapContextFactory = ldapContextFactory + ldapManager._ldapConfiguration = ldapConfiguration + + def username = "admin" + def type = "GROUP" + def name = "CN=test,DC=citrix,DC=com" + + ldapUserManager.getUser(username, type, name, _) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", true, null) + + when: + LdapUser user = ldapManager.getUser(username, type, name) + then: + user.getUsername() == username + user.isDisabled() == true + } + + def "test getUser(username,type,group) when username doesnt exist in ldap"(){ + def ldapUserManager = Mock(LdapUserManager) + def ldapUserManagerFactory = Mock(LdapUserManagerFactory) + ldapUserManagerFactory.getInstance(_) >> ldapUserManager + def ldapContextFactory = Mock(LdapContextFactory) + ldapContextFactory.createBindContext() >> Mock(LdapContext) + def ldapConfiguration = Mock(LdapConfiguration) + + def ldapManager = new LdapManagerImpl() + ldapManager._ldapUserManagerFactory = ldapUserManagerFactory + ldapManager._ldapContextFactory = ldapContextFactory + ldapManager._ldapConfiguration = ldapConfiguration + + def username = "admin" + def type = "GROUP" + def name = "CN=test,DC=citrix,DC=com" + + ldapUserManager.getUser(username, type, name, _) >> { throw new NamingException("Test naming exception") } + + when: + LdapUser user = ldapManager.getUser(username, type, name) + then: + thrown(NoLdapUserMatchingQueryException) + } + def "test getUser(username,type,group) when username is an active member of the group in ldap"(){ + def ldapUserManager = Mock(LdapUserManager) + def ldapUserManagerFactory = Mock(LdapUserManagerFactory) + ldapUserManagerFactory.getInstance(_) >> ldapUserManager + def ldapContextFactory = Mock(LdapContextFactory) + ldapContextFactory.createBindContext() >> Mock(LdapContext) + def ldapConfiguration = Mock(LdapConfiguration) + + def ldapManager = new LdapManagerImpl() + ldapManager._ldapUserManagerFactory = ldapUserManagerFactory + ldapManager._ldapContextFactory = ldapContextFactory + ldapManager._ldapConfiguration = ldapConfiguration + + def username = "admin" + def type = "GROUP" + def name = "CN=test,DC=citrix,DC=com" + + ldapUserManager.getUser(username, type, name, _) >> new LdapUser(username, "email", "firstname", "lastname", "principal", "domain", false, null) + + when: + LdapUser user = ldapManager.getUser(username, type, name) + then: + user.getUsername() == username + user.isDisabled() == false + } } 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 index 1411c29f7b4..8936024c01b 100644 --- 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 @@ -48,7 +48,7 @@ class LdapSearchUserCmdSpec extends spock.lang.Specification { 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", null)) + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null)) ldapManager.searchUsers(_) >> users LdapUserResponse response = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) ldapManager.createLdapUserResponse(_) >> response diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerFactorySpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerFactorySpec.groovy new file mode 100644 index 00000000000..ca423d3e630 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LdapUserManagerFactorySpec.groovy @@ -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 groovy.org.apache.cloudstack.ldap + +import org.apache.cloudstack.ldap.ADLdapUserManagerImpl +import org.apache.cloudstack.ldap.LdapUserManager +import org.apache.cloudstack.ldap.LdapUserManagerFactory +import org.apache.cloudstack.ldap.OpenLdapUserManagerImpl +import org.springframework.beans.factory.config.AutowireCapableBeanFactory +import org.springframework.context.ApplicationContext +import spock.lang.Shared + +class LdapUserManagerFactorySpec extends spock.lang.Specification { + + @Shared + def LdapUserManagerFactory ldapUserManagerFactory; + + def setupSpec() { + ldapUserManagerFactory = new LdapUserManagerFactory(); + ApplicationContext applicationContext = Mock(ApplicationContext); + AutowireCapableBeanFactory autowireCapableBeanFactory = Mock(AutowireCapableBeanFactory); + applicationContext.getAutowireCapableBeanFactory() >> autowireCapableBeanFactory; + ldapUserManagerFactory.setApplicationContext(applicationContext); + } + + def "Test getInstance() from factory"() { + def result = ldapUserManagerFactory.getInstance(id); + + def expected; + if(id == LdapUserManager.Provider.MICROSOFTAD) { + expected = ADLdapUserManagerImpl.class; + } else { + expected = OpenLdapUserManagerImpl.class; + } + + expect: + assert result.class.is(expected) + where: + id << [LdapUserManager.Provider.MICROSOFTAD, LdapUserManager.Provider.OPENLDAP, null] + } +} 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 index 6df947be22a..36b37cad9d0 100644 --- 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 @@ -22,7 +22,7 @@ class LdapUserSpec extends spock.lang.Specification { def "Testing LdapUsers hashCode generation"() { given: - def userA = new LdapUser(usernameA, "", "", "", "", "") + def userA = new LdapUser(usernameA, "", "", "", "", "", false, null) expect: userA.hashCode() == usernameA.hashCode() where: @@ -31,8 +31,8 @@ class LdapUserSpec extends spock.lang.Specification { 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, "", "", "", "", "") + def userA = new LdapUser(usernameA, "", "", "", "", "", false, null) + def userB = new LdapUser(usernameB, "", "", "", "", "", false, null) expect: "That when compared the result is less than or equal to 0" userA.compareTo(userB) <= 0 where: "The following values are used" @@ -43,8 +43,8 @@ class LdapUserSpec extends spock.lang.Specification { def "Testing that LdapUsers equality"() { given: - def userA = new LdapUser(usernameA, "", "", "", "", "") - def userB = new LdapUser(usernameB, "", "", "", "", "") + def userA = new LdapUser(usernameA, "", "", "", "", "", false, null) + def userB = new LdapUser(usernameB, "", "", "", "", "", false, null) expect: userA.equals(userA) == true userA.equals(new Object()) == false @@ -56,7 +56,7 @@ class LdapUserSpec extends spock.lang.Specification { 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, "", "", "", "", "") + def user = new LdapUser(username, "", "", "", "", "", false, null) expect: "The username is equal to the given data source" user.getUsername() == username where: "The username is set to " @@ -65,7 +65,7 @@ class LdapUserSpec extends spock.lang.Specification { 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, "", "", "", "") + def user = new LdapUser("", email, "", "", "", "", false, null) expect: "The email is equal to the given data source" user.getEmail() == email where: "The email is set to " @@ -74,7 +74,7 @@ class LdapUserSpec extends spock.lang.Specification { 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, "", "", "") + def user = new LdapUser("", "", firstname, "", "", "", false, null) expect: "The firstname is equal to the given data source" user.getFirstname() == firstname where: "The firstname is set to " @@ -83,7 +83,7 @@ class LdapUserSpec extends spock.lang.Specification { 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, "", "") + def user = new LdapUser("", "", "", lastname, "", "", false, null) expect: "The lastname is equal to the given data source" user.getLastname() == lastname where: "The lastname is set to " @@ -92,7 +92,7 @@ class LdapUserSpec extends spock.lang.Specification { 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, "") + def user = new LdapUser("", "", "", "", principal, "", false, null) expect: "The principal is equal to the given data source" user.getPrincipal() == principal where: "The principal is set to " @@ -101,7 +101,7 @@ class LdapUserSpec extends spock.lang.Specification { def "Testing the domain is correctly set with the ldap object"() { given: "You have created a LDAP user object with a principal" - def user = new LdapUser("", "", "", "", "", domain) + def user = new LdapUser("", "", "", "", "", domain, false, null) expect: "The principal is equal to the given data source" user.getDomain() == domain where: "The username is set to " diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LinkDomainToLdapCmdSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LinkDomainToLdapCmdSpec.groovy new file mode 100644 index 00000000000..46b00a93d6c --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/LinkDomainToLdapCmdSpec.groovy @@ -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 groovy.org.apache.cloudstack.ldap + +import com.cloud.exception.InvalidParameterValueException +import com.cloud.user.Account +import com.cloud.user.AccountService +import com.cloud.user.User +import com.cloud.user.UserAccount +import org.apache.cloudstack.api.ServerApiException +import org.apache.cloudstack.api.command.LinkDomainToLdapCmd +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse +import org.apache.cloudstack.ldap.LdapManager +import org.apache.cloudstack.ldap.LdapUser +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException +import spock.lang.Shared +import spock.lang.Specification + +class LinkDomainToLdapCmdSpec extends Specification { + + @Shared + private LdapManager _ldapManager; + + @Shared + public AccountService _accountService; + + @Shared + public LinkDomainToLdapCmd linkDomainToLdapCmd; + + def setup() { + _ldapManager = Mock(LdapManager) + _accountService = Mock(AccountService) + + linkDomainToLdapCmd = new LinkDomainToLdapCmd() + linkDomainToLdapCmd._accountService = _accountService + linkDomainToLdapCmd._ldapManager = _ldapManager + } + + def "test invalid params"() { + _ldapManager.linkDomainToLdap(_,_,_,_) >> {throw new InvalidParameterValueException("invalid param")} + when: + linkDomainToLdapCmd.execute(); + then: + thrown(ServerApiException) + } + def "test valid params without admin"(){ + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse("1", "GROUP", "CN=test,DC=ccp,DC=citrix,DC=com", (short)2) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + } + + def "test with valid params and with disabled admin"() { + def domainId = "1"; + def type = "GROUP"; + def name = "CN=test,DC=ccp,DC=Citrix,DC=com" + def accountType = 2; + def username = "admin" + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId, type, name, (short)accountType) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + _ldapManager.getUser(username, type, name) >> new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", name, "ccp", true, null) + + linkDomainToLdapCmd.admin = username + linkDomainToLdapCmd.type = type + linkDomainToLdapCmd.name = name + linkDomainToLdapCmd.domainId = domainId + + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + result.getDomainId() == domainId + result.getType() == type + result.getName() == name + result.getAdminId() == null + } + + def "test with valid params and with admin who exist in cloudstack already"() { + def domainId = 1L; + def type = "GROUP"; + def name = "CN=test,DC=ccp,DC=Citrix,DC=com" + def accountType = 2; + def username = "admin" + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId.toString(), type, name, (short)accountType) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + _ldapManager.getUser(username, type, name) >> new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", name, "ccp", false, null) + + _accountService.getActiveAccountByName(username, domainId) >> Mock(Account) + + linkDomainToLdapCmd.admin = username + linkDomainToLdapCmd.type = type + linkDomainToLdapCmd.name = name + linkDomainToLdapCmd.domainId = domainId + + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + result.getDomainId() == domainId.toString() + result.getType() == type + result.getName() == name + result.getAdminId() == null + } + + def "test with valid params and with admin who doesnt exist in cloudstack"() { + def domainId = 1L; + def type = "GROUP"; + def name = "CN=test,DC=ccp,DC=Citrix,DC=com" + def accountType = 2; + def username = "admin" + def accountId = 24 + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId.toString(), type, name, (short)accountType) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + _ldapManager.getUser(username, type, name) >> new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", name, "ccp", false) + + _accountService.getActiveAccountByName(username, domainId) >> null + UserAccount userAccount = Mock(UserAccount) + userAccount.getAccountId() >> 24 + _accountService.createUserAccount(username, "", "Admin", "Admin", "admin@ccp.citrix.com", null, username, Account.ACCOUNT_TYPE_DOMAIN_ADMIN, domainId, + username, null, _, _, User.Source.LDAP) >> userAccount + + linkDomainToLdapCmd.admin = username + linkDomainToLdapCmd.type = type + linkDomainToLdapCmd.name = name + linkDomainToLdapCmd.domainId = domainId + + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + result.getDomainId() == domainId.toString() + result.getType() == type + result.getName() == name + result.getAdminId() == String.valueOf(accountId) + } + + def "test when admin doesnt exist in ldap"() { + def domainId = 1L; + def type = "GROUP"; + def name = "CN=test,DC=ccp,DC=Citrix,DC=com" + def accountType = 2; + def username = "admin" + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId.toString(), type, name, (short)accountType) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + _ldapManager.getUser(username, type, name) >> {throw new NoLdapUserMatchingQueryException("get ldap user failed from mock")} + + linkDomainToLdapCmd.admin = username + linkDomainToLdapCmd.type = type + linkDomainToLdapCmd.name = name + linkDomainToLdapCmd.domainId = domainId + + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + result.getDomainId() == domainId.toString() + result.getType() == type + result.getName() == name + result.getAdminId() == null + } + + /** + * api should not fail in this case as link domain to ldap is successful + */ + def "test when create user account throws a run time exception"() { + def domainId = 1L; + def type = "GROUP"; + def name = "CN=test,DC=ccp,DC=Citrix,DC=com" + def accountType = 2; + def username = "admin" + def accountId = 24 + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId.toString(), type, name, (short)accountType) + _ldapManager.linkDomainToLdap(_,_,_,_) >> response + _ldapManager.getUser(username, type, name) >> new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", name, "ccp", false, null) + + _accountService.getActiveAccountByName(username, domainId) >> null + UserAccount userAccount = Mock(UserAccount) + userAccount.getAccountId() >> 24 + _accountService.createUserAccount(username, "", "Admin", "Admin", "admin@ccp.citrix.com", null, username, Account.ACCOUNT_TYPE_DOMAIN_ADMIN, domainId, + username, null, _, _, User.Source.LDAP) >> { throw new RuntimeException("created failed from mock") } + + linkDomainToLdapCmd.admin = username + linkDomainToLdapCmd.type = type + linkDomainToLdapCmd.name = name + linkDomainToLdapCmd.domainId = domainId + + when: + linkDomainToLdapCmd.execute() + then: + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject() + result.getObjectName() == "LinkDomainToLdap" + result.getResponseName() == linkDomainToLdapCmd.getCommandName() + result.getDomainId() == domainId.toString() + result.getType() == type + result.getName() == name + result.getAdminId() == null + } + +} diff --git a/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/OpenLdapUserManagerSpec.groovy b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/OpenLdapUserManagerSpec.groovy new file mode 100644 index 00000000000..40daa4110fc --- /dev/null +++ b/plugins/user-authenticators/ldap/test/groovy/org/apache/cloudstack/ldap/OpenLdapUserManagerSpec.groovy @@ -0,0 +1,335 @@ +// 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.OpenLdapUserManagerImpl +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.InitialLdapContext +import javax.naming.ldap.LdapContext + +class OpenLdapUserManagerSpec 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 createGroupSearchContextOneUser() { + + def umSearchResult = Mock(SearchResult) + umSearchResult.getName() >> principal; + umSearchResult.getAttributes() >> principal + + def uniqueMembers = new BasicNamingEnumerationImpl() + uniqueMembers.add(umSearchResult); + def attributes = Mock(Attributes) + def uniqueMemberAttribute = Mock(Attribute) + uniqueMemberAttribute.getId() >> "uniquemember" + uniqueMemberAttribute.getAll() >> uniqueMembers + attributes.get("uniquemember") >> uniqueMemberAttribute + + def groupSearchResult = Mock(SearchResult) + groupSearchResult.getName() >> principal; + groupSearchResult.getAttributes() >> attributes + + def searchGroupResults = new BasicNamingEnumerationImpl() + searchGroupResults.add(groupSearchResult); + + attributes = createUserAttributes(username, email, firstname, lastname) + SearchResult userSearchResult = createSearchResult(attributes) + def searchUsersResults = new BasicNamingEnumerationImpl() + searchUsersResults.add(userSearchResult); + + def context = Mock(LdapContext) + context.search(_, _, _) >>> [searchGroupResults, searchUsersResults, searchGroupResults, new BasicNamingEnumerationImpl()]; + + return context + } + + private def createGroupSearchContextNoUser() { + + def umSearchResult = Mock(SearchResult) + umSearchResult.getName() >> principal; + umSearchResult.getAttributes() >> principal + + def uniqueMembers = new BasicNamingEnumerationImpl() + uniqueMembers.add(umSearchResult); + def attributes = Mock(Attributes) + def uniqueMemberAttribute = Mock(Attribute) + uniqueMemberAttribute.getId() >> "uniquemember" + uniqueMemberAttribute.getAll() >> uniqueMembers + attributes.get("uniquemember") >> uniqueMemberAttribute + + def groupSearchResult = Mock(SearchResult) + groupSearchResult.getName() >> principal; + groupSearchResult.getAttributes() >> attributes + + def searchGroupResults = new BasicNamingEnumerationImpl() + searchGroupResults.add(groupSearchResult); + + def context = Mock(LdapContext) + context.search(_, _, _) >>> [searchGroupResults, new BasicNamingEnumerationImpl()]; + + return context + } + + 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 + search.getNameInNamespace() >> principal + + 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" + ldapConfiguration.getCommonNameAttribute() >> "cn" + ldapConfiguration.getGroupObject() >> "groupOfUniqueNames" + ldapConfiguration.getGroupUniqueMemberAttribute(_) >> "uniquemember" + ldapConfiguration.getLdapPageSize() >> 1 + ldapConfiguration.getReadTimeout(_) >> 1000 + + 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 OpenLdapUserManagerImpl(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 OpenLdapUserManagerImpl(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 OpenLdapUserManagerImpl(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 ldap user from searchUsers"() { + given: "We have a LdapUserManager" + def userManager = new OpenLdapUserManagerImpl(ldapConfiguration) + + when: "We search for users" + def result = userManager.searchUsers(createContext()) + + then: "A list of users are returned." + result.first().getPrincipal() == principal + } + + def "Test successfully returning an Ldap user from a get user request"() { + given: "We have a LdapUserMaanger" + + def userManager = new OpenLdapUserManagerImpl(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 OpenLdapUserManagerImpl(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 OpenLdapUserManagerImpl(); + expect: "The result is not null" + result != null + } + + def "test successful generateGroupSearchFilter"() { + given: "ldap user manager and ldap config" + def ldapUserManager = new OpenLdapUserManagerImpl(ldapConfiguration) + def groupName = varGroupName == null ? "*" : varGroupName + def expectedResult = "(&(objectClass=groupOfUniqueNames)(cn=" + groupName + "))"; + + def result = ldapUserManager.generateGroupSearchFilter(varGroupName) + expect: + result == expectedResult + where: "The group name passed is set to " + varGroupName << ["", null, "Murphy"] + } + + def "test successful getUsersInGroup one user"() { + given: "ldap user manager and ldap config" + def ldapUserManager = new OpenLdapUserManagerImpl(ldapConfiguration) + + when: "A request for users is made" + def result = ldapUserManager.getUsersInGroup("engineering", createGroupSearchContextOneUser(),) + then: "one user is returned" + result.size() == 1 + } + + def "test successful getUsersInGroup no user"() { + given: "ldap user manager and ldap config" + def ldapUserManager = new OpenLdapUserManagerImpl(ldapConfiguration) + + when: "A request for users is made" + def result = ldapUserManager.getUsersInGroup("engineering", createGroupSearchContextNoUser(),) + then: "no user is returned" + result.size() == 0 + } + + def "test successful getUserForDn"() { + given: "ldap user manager and ldap config" + def ldapUserManager = new OpenLdapUserManagerImpl(ldapConfiguration) + + when: "A request for users is made" + def result = ldapUserManager.getUserForDn("cn=Ryan Murphy,ou=engineering,dc=cloudstack,dc=org", createContext()) + then: "A list of users is returned" + result != 1 + result.username == username + result.email == email + result.firstname == firstname + result.lastname == lastname + result.principal == principal + + } + + def "test searchUsers when ldap basedn in not set"() { + given: "ldap configuration where basedn is not set" + def ldapconfig = Mock(LdapConfiguration) + ldapconfig.getBaseDn() >> null + def ldapUserManager = new OpenLdapUserManagerImpl(ldapconfig) + + when: "A request for search users is made" + def result = ldapUserManager.searchUsers(new InitialLdapContext()) + + then: "An exception with no basedn defined is returned" + def e = thrown(IllegalArgumentException) + e.message == "ldap basedn is not configured" + } +} diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapConfigurationChanger.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapConfigurationChanger.java new file mode 100644 index 00000000000..d8c0c7af561 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapConfigurationChanger.java @@ -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 org.apache.cloudstack.api.command; + +import java.lang.reflect.Field; + +class LdapConfigurationChanger { + /** + * sets a possibly not accessible field of the target object. + * @param target the object to set a hidden fields value in. + * @param name the name of the field to set. + * @param o intended value for the field "name" + * @throws IllegalAccessException + * @throws NoSuchFieldException + */ + protected void setHiddenField(Object target, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException { + Class klas = target.getClass(); + Field f = getFirstFoundField(name, klas); + f.setAccessible(true); + f.set(target, o); + } + + /** + * the first field found by this name in the class "klas" or any of it's superclasses except for {@code Object}. Implementers of this interface can decide to also return any field in implemented interfaces or in {@code Object}. + * + * @param name of the field to find + * @param klas class to gat a field by name "name" from + * @return a {@code Field} by the name "name" + * @throws NoSuchFieldException + */ + protected Field getFirstFoundField(String name, Class klas) throws NoSuchFieldException { + try { + return klas.getDeclaredField(name); + } catch (NoSuchFieldException e) { + Class parent = klas.getSuperclass(); + if(parent.equals(Object.class)) { + throw e; + } + return getFirstFoundField(name, parent); + } + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java new file mode 100644 index 00000000000..f901f7778fa --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.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.api.command; + +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isNull; +import static org.powermock.api.mockito.PowerMockito.spy; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LdapCreateAccountCmdTest extends LdapConfigurationChanger { + @Mock + LdapManager ldapManager; + @Mock + AccountService accountService; + @Mock + RoleService roleService; + + LdapCreateAccountCmd ldapCreateAccountCmd; + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + ldapCreateAccountCmd = spy(new LdapCreateAccountCmd(ldapManager, accountService)); + ldapCreateAccountCmd.roleService = roleService; + setHiddenField(ldapCreateAccountCmd,"accountType", Account.ACCOUNT_TYPE_DOMAIN_ADMIN); + } + + @Test(expected = ServerApiException.class) + public void failureToRetrieveLdapUser() throws Exception { + // We have an LdapManager, AccountService and LdapCreateAccountCmd and LDAP user that doesn't exist + when(ldapManager.getUser(anyString(), isNull(Long.class))).thenThrow(NoLdapUserMatchingQueryException.class); + ldapCreateAccountCmd.execute(); + fail("An exception should have been thrown: " + ServerApiException.class); + } + + @Test(expected = ServerApiException.class) + public void failedCreationDueToANullResponseFromCloudstackAccountCreater() throws Exception { + // We have an LdapManager, AccountService and LdapCreateAccountCmd + LdapUser mrMurphy = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null); + when(ldapManager.getUser(anyString(), isNull(Long.class))).thenReturn(mrMurphy); + ldapCreateAccountCmd.execute(); + fail("An exception should have been thrown: " + ServerApiException.class); + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java new file mode 100644 index 00000000000..e8b35a88fd7 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java @@ -0,0 +1,85 @@ +// 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 com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.DomainService; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.apache.cloudstack.api.response.LdapUserResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.powermock.api.mockito.PowerMockito.spy; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LdapImportUsersCmdTest extends LdapConfigurationChanger { + @Mock + LdapManager ldapManager; + @Mock + AccountService accountService; + @Mock + DomainService domainService; + @Mock + RoleService roleService; + + LdapImportUsersCmd ldapImportUsersCmd; + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + ldapImportUsersCmd = spy(new LdapImportUsersCmd(ldapManager, domainService, accountService)); + ldapImportUsersCmd.roleService = roleService; + setHiddenField(ldapImportUsersCmd, "accountType", Account.ACCOUNT_TYPE_DOMAIN_ADMIN); + } + + @Test + public void successfulResponseFromExecute() throws Exception { + List users = new ArrayList(); + users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)); + users.add(new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering", false, null)); + when(ldapManager.getUsers(null)).thenReturn(users); + LdapUserResponse response1 = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering"); + LdapUserResponse response2 = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", "engineering"); + when(ldapManager.createLdapUserResponse(any(LdapUser.class))).thenReturn(response1).thenReturn(response2); + + + Domain domain = new DomainVO("engineering", 1L, 1L, "engineering", UUID.randomUUID().toString()); + when(domainService.getDomainByName("engineering", 1L)).thenReturn(null, domain); + when(domainService.createDomain(eq("engineering"), eq(1L), eq("engineering"), anyString())).thenReturn(domain); + + ldapImportUsersCmd.execute(); + ListResponse resp = (ListResponse)ldapImportUsersCmd.getResponseObject(); + assertEquals(" when LdapListUsersCmd is executed, a list of size 2 should be returned", 2, resp.getResponses().size()); + } +} \ No newline at end of file diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java new file mode 100644 index 00000000000..b4a89baf669 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java @@ -0,0 +1,97 @@ +// 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 com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.User; +import com.cloud.user.UserAccountVO; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.LinkAccountToLdapResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LinkAccountToLdapCmdTest extends LdapConfigurationChanger { + + @Mock + LdapManager ldapManager; + @Mock + AccountService accountService; + + LinkAccountToLdapCmd linkAccountToLdapCmd; + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + linkAccountToLdapCmd = new LinkAccountToLdapCmd(); + setHiddenField(linkAccountToLdapCmd, "_ldapManager", ldapManager); + setHiddenField(linkAccountToLdapCmd, "_accountService", accountService); + } + + @Test + public void execute() throws Exception { + // test with valid params and with admin who doesnt exist in cloudstack + long domainId = 1; + String type = "GROUP"; + String ldapDomain = "CN=test,DC=ccp,DC=Citrix,DC=com"; + short accountType = Account.ACCOUNT_TYPE_DOMAIN_ADMIN; + String username = "admin"; + long accountId = 24; + String accountName = "test"; + + setHiddenField(linkAccountToLdapCmd, "ldapDomain", ldapDomain); + setHiddenField(linkAccountToLdapCmd, "admin", username); + setHiddenField(linkAccountToLdapCmd, "type", type); + setHiddenField(linkAccountToLdapCmd, "domainId", domainId); + setHiddenField(linkAccountToLdapCmd, "accountType", accountType); + setHiddenField(linkAccountToLdapCmd, "accountName", accountName); + + + LinkAccountToLdapResponse response = new LinkAccountToLdapResponse(String.valueOf(domainId), type, ldapDomain, (short)accountType, username, accountName); + when(ldapManager.linkAccountToLdap(linkAccountToLdapCmd)).thenReturn(response); + when(ldapManager.getUser(username, type, ldapDomain, 1L)) + .thenReturn(new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", ldapDomain, "ccp", false, null)); + + when(accountService.getActiveAccountByName(username, domainId)).thenReturn(null); + UserAccountVO userAccount = new UserAccountVO(); + userAccount.setAccountId(24); + when(accountService.createUserAccount(eq(username), eq(""), eq("Admin"), eq("Admin"), eq("admin@ccp.citrix.com"), isNull(String.class), + eq(username), eq(Account.ACCOUNT_TYPE_DOMAIN_ADMIN), eq(RoleType.DomainAdmin.getId()), eq(domainId), isNull(String.class), + (java.util.Map)isNull(), anyString(), anyString(), eq(User.Source.LDAP))).thenReturn(userAccount); + + linkAccountToLdapCmd.execute(); + LinkAccountToLdapResponse result = (LinkAccountToLdapResponse)linkAccountToLdapCmd.getResponseObject(); + assertEquals("objectName", linkAccountToLdapCmd.APINAME, result.getObjectName()); + assertEquals("commandName", linkAccountToLdapCmd.getCommandName(), result.getResponseName()); + assertEquals("domainId", String.valueOf(domainId), result.getDomainId()); + assertEquals("type", type, result.getType()); + assertEquals("name", ldapDomain, result.getLdapDomain()); + assertEquals("accountId", String.valueOf(accountId), result.getAdminId()); + } +} diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java new file mode 100644 index 00000000000..cc6479e2399 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.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 com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.User; +import com.cloud.user.UserAccountVO; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.LinkDomainToLdapResponse; +import org.apache.cloudstack.ldap.LdapManager; +import org.apache.cloudstack.ldap.LdapUser; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LinkDomainToLdapCmdTest extends LdapConfigurationChanger +{ + @Mock + LdapManager ldapManager; + @Mock + AccountService accountService; + + LinkDomainToLdapCmd linkDomainToLdapCmd; + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + linkDomainToLdapCmd = new LinkDomainToLdapCmd(); + setHiddenField(linkDomainToLdapCmd, "_ldapManager", ldapManager); + setHiddenField(linkDomainToLdapCmd, "_accountService", accountService); + } + + @After + public void tearDown() { + } + + @Test + public void execute() throws Exception { +// test with valid params and with admin who doesnt exist in cloudstack + Long domainId = 1L; + String type = "GROUP"; + String ldapDomain = "CN=test,DC=ccp,DC=Citrix,DC=com"; + short accountType = Account.ACCOUNT_TYPE_DOMAIN_ADMIN; + String username = "admin"; + long accountId = 24; + setHiddenField(linkDomainToLdapCmd, "ldapDomain", ldapDomain); + setHiddenField(linkDomainToLdapCmd, "admin", username); + setHiddenField(linkDomainToLdapCmd, "type", type); + setHiddenField(linkDomainToLdapCmd, "domainId", domainId); + setHiddenField(linkDomainToLdapCmd, "accountType", accountType); + + LinkDomainToLdapResponse response = new LinkDomainToLdapResponse(domainId.toString(), type, ldapDomain, (short)accountType); + when(ldapManager.linkDomainToLdap(linkDomainToLdapCmd)).thenReturn(response); + when(ldapManager.getUser(username, type, ldapDomain, 1L)).thenReturn(new LdapUser(username, "admin@ccp.citrix.com", "Admin", "Admin", ldapDomain, "ccp", false, null)); + + when(accountService.getActiveAccountByName(username, domainId)).thenReturn(null); + UserAccountVO userAccount = new UserAccountVO(); + userAccount.setAccountId(24); + when(accountService.createUserAccount(eq(username), eq(""), eq("Admin"), eq("Admin"), eq("admin@ccp.citrix.com"), isNull(String.class), + eq(username), eq(Account.ACCOUNT_TYPE_DOMAIN_ADMIN), eq(RoleType.DomainAdmin.getId()), eq(domainId), isNull(String.class), + (java.util.Map)isNull(), anyString(), anyString(), eq(User.Source.LDAP))).thenReturn(userAccount); + + + linkDomainToLdapCmd.execute(); + LinkDomainToLdapResponse result = (LinkDomainToLdapResponse)linkDomainToLdapCmd.getResponseObject(); + assertEquals("objectName", "LinkDomainToLdap", result.getObjectName()); + assertEquals("commandName", linkDomainToLdapCmd.getCommandName(), result.getResponseName()); + assertEquals("domainId", domainId.toString(), result.getDomainId()); + assertEquals("type", type, result.getType()); + assertEquals("name", ldapDomain, result.getLdapDomain()); + assertEquals("accountId", String.valueOf(accountId), result.getAdminId()); + } + +} diff --git a/plugins/user-authenticators/ldap/test/org/apache/cloudstack/ldap/LdapConfigurationTest.java b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/ldap/LdapConfigurationTest.java new file mode 100644 index 00000000000..52c70ac0d19 --- /dev/null +++ b/plugins/user-authenticators/ldap/test/org/apache/cloudstack/ldap/LdapConfigurationTest.java @@ -0,0 +1,141 @@ +// 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 org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.ldap.dao.LdapConfigurationDao; +import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class LdapConfigurationTest { + + LdapConfigurationDao ldapConfigurationDao; + LdapConfiguration ldapConfiguration; + + private void overrideConfigValue(final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException { + Field configKey = LdapConfiguration.class.getDeclaredField(configKeyName); + configKey.setAccessible(true); + + ConfigKey key = (ConfigKey)configKey.get(ldapConfiguration); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(configKey, configKey.getModifiers() & ~Modifier.FINAL); + + Field f = ConfigKey.class.getDeclaredField("_value"); + f.setAccessible(true); + modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); + f.set(key, o); + + Field dynamic = ConfigKey.class.getDeclaredField("_isDynamic"); + dynamic.setAccessible(true); + modifiersField.setInt(dynamic, dynamic.getModifiers() & ~Modifier.FINAL); + dynamic.setBoolean(key, false); + } + + @Before + public void init() throws Exception { + ldapConfigurationDao = new LdapConfigurationDaoImpl(); + ldapConfiguration = new LdapConfiguration(ldapConfigurationDao);; + } + + @Test + public void getAuthenticationReturnsSimple() throws Exception { + overrideConfigValue("ldapBindPrincipal", "cn=bla"); + overrideConfigValue("ldapBindPassword", "pw"); + String authentication = ldapConfiguration.getAuthentication(null); + assertEquals("authentication should be set to simple", "simple", authentication); + } + + + @Test + public void getBaseDnReturnsABaseDn() throws Exception { + overrideConfigValue("ldapBaseDn", "dc=cloudstack,dc=org"); + String baseDn = ldapConfiguration.getBaseDn(null); + assertEquals("The set baseDn should be returned","dc=cloudstack,dc=org", baseDn); + } + + @Test + public void getGroupUniqueMemberAttribute() throws Exception { + String [] groupNames = {"bla", "uniquemember", "memberuid", "", null}; + for (String groupObject: groupNames) { + overrideConfigValue("ldapGroupUniqueMemberAttribute", groupObject); + String expectedResult = null; + if(groupObject == null) { + expectedResult = "uniquemember"; + } else { + expectedResult = groupObject; + }; + String result = ldapConfiguration.getGroupUniqueMemberAttribute(null); + assertEquals("testing for " + groupObject, expectedResult, result); + } + } + + @Test + public void getSSLStatusCanBeTrue() throws Exception { +// given: "We have a ConfigDao with values for truststore and truststore password set" + overrideConfigValue("ldapTrustStore", "/tmp/ldap.ts"); + overrideConfigValue("ldapTrustStorePassword", "password"); + + assertTrue("A request is made to get the status of SSL should result in true", ldapConfiguration.getSSLStatus()); + } + @Test + public void getSearchGroupPrincipleReturnsSuccessfully() throws Exception { + // We have a ConfigDao with a value for ldap.search.group.principle and an LdapConfiguration + overrideConfigValue("ldapSearchGroupPrinciple", "cn=cloudstack,cn=users,dc=cloudstack,dc=org"); + String result = ldapConfiguration.getSearchGroupPrinciple(null); + + assertEquals("The result holds the same value configDao did", "cn=cloudstack,cn=users,dc=cloudstack,dc=org",result); + } + + @Test + public void getTrustStorePasswordResopnds() throws Exception { + // We have a ConfigDao with a value for truststore password + overrideConfigValue("ldapTrustStorePassword", "password"); + + String result = ldapConfiguration.getTrustStorePassword(); + + assertEquals("The result is password", "password", result); + } + + + @Test + public void getGroupObject() throws Exception { + String [] groupNames = {"bla", "groupOfUniqueNames", "groupOfNames", "", null}; + for (String groupObject: groupNames) { + overrideConfigValue("ldapGroupObject", groupObject); + String expectedResult = null; + if(groupObject == null) { + expectedResult = "groupOfUniqueNames"; + } else { + expectedResult = groupObject; + }; + String result = ldapConfiguration.getGroupObject(null); + assertEquals("testing for " + groupObject, expectedResult, result); + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index aaa9ac5076f..bc37d151467 100644 --- a/pom.xml +++ b/pom.xml @@ -82,8 +82,13 @@ 1.5.3 1.3.22 2.6 - 2.5 + 3.6 + 2.6 + 1.3.3 + 4.1 + 1.6 0.9.8 + 3.22.0-GA 0.10 build/replace.properties 0.5.1 diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 5d78a3d75e0..3370ebb6fb7 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -38,6 +38,10 @@ import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; +/** + * @deprecated use the more dynamic ConfigKey + */ +@Deprecated public enum Config { // Alert @@ -1860,42 +1864,6 @@ public enum Config { + "If it is set to -1, then it means always use single-part upload to upload object to S3. ", 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), - LdapGroupObject("Advanced", ManagementServer.class, String.class, "ldap.group.object", "groupOfUniqueNames", "Sets the object type of groups within LDAP", null), - LdapGroupUniqueMemberAttribute( - "Advanced", - ManagementServer.class, - String.class, - "ldap.group.user.uniquemember", - "uniquemember", - "Sets the attribute for uniquemembers within a group", - null), - // VMSnapshots VMSnapshotMax("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.max", "10", "Maximum vm snapshots for a vm", null), VMSnapshotCreateWait("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.create.wait", "1800", "In second, timeout for create vm snapshot", null), @@ -2019,17 +1987,6 @@ public enum Config { _scope = ConfigKey.Scope.Global.toString(); } - private Config(String category, Class componentClass, Class type, String name, String defaultValue, String description, String range, String scope) { - _category = category; - _componentClass = componentClass; - _type = type; - _name = name; - _defaultValue = defaultValue; - _description = description; - _range = range; - _scope = scope; - } - public String getCategory() { return _category; } @@ -2050,10 +2007,6 @@ public enum Config { return _type; } - public Class getComponentClass() { - return _componentClass; - } - public String getScope() { return _scope; } @@ -2121,8 +2074,4 @@ public enum Config { } return categories; } - - public static List getConfigListByScope(String scope) { - return s_scopeLevelConfigsMap.get(scope); - } } diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 9de88e0ccb5..c2fdd338f10 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -124,8 +124,10 @@ import com.cloud.dc.dao.VlanDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeploymentClusterPlanner; import com.cloud.domain.Domain; +import com.cloud.domain.DomainDetailVO; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; +import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; @@ -309,6 +311,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject AccountDetailsDao _accountDetailsDao; @Inject + DomainDetailsDao _domainDetailsDao; + @Inject PrimaryDataStoreDao _storagePoolDao; @Inject NicSecondaryIpDao _nicSecondaryIpDao; @@ -507,6 +511,22 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati _accountDetailsDao.update(accountDetailVO.getId(), accountDetailVO); } break; + + case Domain: + final DomainVO domain = _domainDao.findById(resourceId); + if (domain == null) { + throw new InvalidParameterValueException("unable to find domain by id " + resourceId); + } + DomainDetailVO domainDetailVO = _domainDetailsDao.findDetail(resourceId, name); + if (domainDetailVO == null) { + domainDetailVO = new DomainDetailVO(resourceId, name, value); + _domainDetailsDao.persist(domainDetailVO); + } else { + domainDetailVO.setValue(value); + _domainDetailsDao.update(domainDetailVO.getId(), domainDetailVO); + } + break; + default: throw new InvalidParameterValueException("Scope provided is invalid"); } @@ -616,7 +636,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long clusterId = cmd.getClusterId(); Long storagepoolId = cmd.getStoragepoolId(); Long accountId = cmd.getAccountId(); - CallContext.current().setEventDetails(" Name: " + name + " New Value: " + (((name.toLowerCase()).contains("password")) ? "*****" : (((value == null) ? "" : value)))); + final Long domainId = cmd.getDomainId(); + CallContext.current().setEventDetails(" Name: " + name + " New Value: " + (name.toLowerCase().contains("password") ? "*****" : value == null ? "" : value)); // check if config value exists ConfigurationVO config = _configDao.findByName(name); String catergory = null; @@ -661,6 +682,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati id = accountId; paramCountCheck++; } + if (domainId != null) { + scope = ConfigKey.Scope.Domain.toString(); + id = domainId; + paramCountCheck++; + } if (storagepoolId != null) { scope = ConfigKey.Scope.StoragePool.toString(); id = storagepoolId; diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 8eb30210848..b24e9968d36 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -221,6 +221,7 @@ import org.apache.cloudstack.api.command.admin.user.EnableUserCmd; import org.apache.cloudstack.api.command.admin.user.GetUserCmd; import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; import org.apache.cloudstack.api.command.admin.user.LockUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.RegisterCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; @@ -1642,13 +1643,14 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe Filter searchFilter = new Filter(ConfigurationVO.class, "name", true, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchCriteria sc = _configDao.createSearchCriteria(); - Object name = cmd.getConfigName(); - Object category = cmd.getCategory(); - Object keyword = cmd.getKeyword(); - Long zoneId = cmd.getZoneId(); - Long clusterId = cmd.getClusterId(); - Long storagepoolId = cmd.getStoragepoolId(); - Long accountId = cmd.getAccountId(); + final Object name = cmd.getConfigName(); + final Object category = cmd.getCategory(); + final Object keyword = cmd.getKeyword(); + final Long zoneId = cmd.getZoneId(); + final Long clusterId = cmd.getClusterId(); + final Long storagepoolId = cmd.getStoragepoolId(); + final Long accountId = cmd.getAccountId(); + final Long domainId = cmd.getDomainId(); String scope = null; Long id = null; int paramCountCheck = 0; @@ -1668,6 +1670,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe id = accountId; paramCountCheck++; } + if (domainId != null) { + scope = ConfigKey.Scope.Domain.toString(); + id = domainId; + paramCountCheck++; + } if (storagepoolId != null) { scope = ConfigKey.Scope.StoragePool.toString(); id = storagepoolId; @@ -1719,7 +1726,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe if (configVo != null) { ConfigKey key = _configDepot.get(param.getName()); if (key != null) { - configVo.setValue(key.valueIn(id).toString()); + configVo.setValue(key.valueIn(id) == null ? null : key.valueIn(id).toString()); configVOList.add(configVo); } else { s_logger.warn("ConfigDepot could not find parameter " + param.getName() + " for scope " + scope); @@ -2680,6 +2687,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(GetUserCmd.class); cmdList.add(ListUsersCmd.class); cmdList.add(LockUserCmd.class); + cmdList.add(MoveUserCmd.class); cmdList.add(RegisterCmd.class); cmdList.add(UpdateUserCmd.class); cmdList.add(CreateVlanIpRangeCmd.class); diff --git a/server/src/com/cloud/user/AccountManager.java b/server/src/com/cloud/user/AccountManager.java index 2cae32eadff..331f4188ac1 100755 --- a/server/src/com/cloud/user/AccountManager.java +++ b/server/src/com/cloud/user/AccountManager.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import com.cloud.api.query.vo.ControlledViewEntity; @@ -152,10 +153,17 @@ public interface AccountManager extends AccountService { */ boolean deleteUser(DeleteUserCmd deleteUserCmd); + /** + * moves a user to another account within the same domain + * @param moveUserCmd + * @return true if the user was successfully moved + */ + boolean moveUser(MoveUserCmd moveUserCmd); + /** * Update a user by userId * - * @param userId + * @param cmd * @return UserAccount object */ UserAccount updateUser(UpdateUserCmd cmd); @@ -197,4 +205,6 @@ public interface AccountManager extends AccountService { public static final String MESSAGE_ADD_ACCOUNT_EVENT = "Message.AddAccount.Event"; public static final String MESSAGE_REMOVE_ACCOUNT_EVENT = "Message.RemoveAccount.Event"; + + boolean moveUser(long id, Long domainId, long accountId); } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index dced8a8936a..a641ae5aca5 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -16,6 +16,28 @@ // under the License. package com.cloud.user; +import java.net.URLEncoder; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.ControlledViewEntity; import com.cloud.configuration.Config; @@ -128,6 +150,7 @@ import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.RegisterCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import org.apache.cloudstack.context.CallContext; @@ -142,27 +165,6 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import javax.crypto.KeyGenerator; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import javax.ejb.Local; -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.net.URLEncoder; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - @Local(value = {AccountManager.class, AccountService.class}) public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager { public static final Logger s_logger = Logger.getLogger(AccountManagerImpl.class); @@ -1662,29 +1664,106 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M @Override @ActionEvent(eventType = EventTypes.EVENT_USER_DELETE, eventDescription = "deleting User") public boolean deleteUser(DeleteUserCmd deleteUserCmd) { - long id = deleteUserCmd.getId(); - - UserVO user = _userDao.findById(id); - - if (user == null) { - throw new InvalidParameterValueException("The specified user doesn't exist in the system"); - } + UserVO user = getValidUserVO(deleteUserCmd.getId()); Account account = _accountDao.findById(user.getAccountId()); + // don't allow to delete the user from the account of type Project + checkAccountAndAccess(user, account); + return _userDao.remove(deleteUserCmd.getId()); + } + + @ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account") + public boolean moveUser(final MoveUserCmd cmd) { + final UserVO user = getValidUserVO(cmd.getId()); + Account oldAccount = _accountDao.findById(user.getAccountId()); + checkAccountAndAccess(user, oldAccount); + long domainId = oldAccount.getDomainId(); + + final long newAccountId = getNewAccountId(domainId, cmd.getAccountName(), cmd.getAccountId()); + + return moveUser(user, newAccountId); + } + + public boolean moveUser(long id, Long domainId, long accountId) { + UserVO user = getValidUserVO(id); + Account oldAccount = _accountDao.findById(user.getAccountId()); + checkAccountAndAccess(user, oldAccount); + Account newAccount = _accountDao.findById(accountId); + checkIfNotMovingAcrossDomains(domainId, newAccount); + return moveUser(user , accountId); + } + + private boolean moveUser(final UserVO user, final long newAccountId) { + if(newAccountId == user.getAccountId()) { + // could do a not silent fail but the objective of the user is reached + return true; // no need to create a new user object for this user + } + + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + UserVO newUser = new UserVO(user); + user.setExternalEntity(user.getUuid()); + user.setUuid(UUID.randomUUID().toString()); + _userDao.update(user.getId(),user); + newUser.setAccountId(newAccountId); + boolean success = _userDao.remove(user.getId()); + UserVO persisted = _userDao.persist(newUser); + return success && persisted.getUuid().equals(user.getExternalEntity()); + } + }); + } + + private long getNewAccountId(long domainId, String accountName, Long accountId) { + Account newAccount = null; + if (StringUtils.isNotBlank(accountName)) { + if(s_logger.isDebugEnabled()) { + s_logger.debug("Getting id for account by name '" + accountName + "' in domain " + domainId); + } + newAccount = _accountDao.findEnabledAccount(accountName, domainId); + } + if (newAccount == null && accountId != null) { + newAccount = _accountDao.findById(accountId); + } + if (newAccount == null) { + throw new CloudRuntimeException("no account name or account id. this should have been caught before this point"); + } + + checkIfNotMovingAcrossDomains(domainId, newAccount); + return newAccount.getAccountId(); + } + + private void checkIfNotMovingAcrossDomains(long domainId, Account newAccount) { + if(newAccount.getDomainId() != domainId) { + // not in scope + throw new InvalidParameterValueException("moving a user from an account in one domain to an account in annother domain is not supported!"); + } + } + + private void checkAccountAndAccess(UserVO user, Account account) { // don't allow to delete the user from the account of type Project if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Project users cannot be deleted or moved."); + } + + checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account); + CallContext.current().putContextParameter(User.class, user.getUuid()); + } + + private UserVO getValidUserVO(long id) { + UserVO user = _userDao.findById(id); + + if (user == null || user.getRemoved() != null) { throw new InvalidParameterValueException("The specified user doesn't exist in the system"); } // don't allow to delete default user (system and admin users) if (user.isDefault()) { - throw new InvalidParameterValueException("The user is default and can't be removed"); + throw new InvalidParameterValueException("The user is default and can't be (re)moved"); } - checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account); - CallContext.current().putContextParameter(User.class, user.getUuid()); - return _userDao.remove(id); + return user; } protected class AccountCleanupTask extends ManagedContextRunnable { @@ -2124,15 +2203,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M s_logger.debug("Attempting to log in user: " + username + " in domain " + domainId); } UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId); - if (userAccount == null) { - s_logger.warn("Unable to find an user with username " + username + " in domain " + domainId); - return null; - } boolean authenticated = false; HashSet actionsOnFailedAuthenticaion = new HashSet(); - User.Source userSource = userAccount.getSource(); + User.Source userSource = userAccount != null ? userAccount.getSource() : User.Source.UNKNOWN; for (UserAuthenticator authenticator : _userAuthenticators) { + if(s_logger.isTraceEnabled()) { + s_logger.trace("authenticating '" + username + "' (source: '" + userSource + "') with authenticator " + authenticator.getName()); + } if(userSource != User.Source.UNKNOWN) { if(!authenticator.getName().equalsIgnoreCase(userSource.name())){ continue; @@ -2156,6 +2234,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M if (domain != null) { domainName = domain.getName(); } + userAccount = _userAccountDao.getUserAccount(username, domainId); if (!userAccount.getState().equalsIgnoreCase(Account.State.enabled.toString()) || !userAccount.getAccountState().equalsIgnoreCase(Account.State.enabled.toString())) { diff --git a/server/src/org/apache/cloudstack/region/RegionManager.java b/server/src/org/apache/cloudstack/region/RegionManager.java index 6f254817103..f7d7c10c5b9 100644 --- a/server/src/org/apache/cloudstack/region/RegionManager.java +++ b/server/src/org/apache/cloudstack/region/RegionManager.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import com.cloud.domain.Domain; @@ -122,10 +123,17 @@ public interface RegionManager { */ boolean deleteUser(DeleteUserCmd deleteUserCmd); + /** + * Deletes user by Id + * @param moveUserCmd + * @return + */ + boolean moveUser(MoveUserCmd moveUserCmd); + /** * update an existing domain * - * @param cmd + * @param updateDomainCmd * - the command containing domainId and new domainName * @return Domain object if the command succeeded */ @@ -142,7 +150,7 @@ public interface RegionManager { /** * Update a user by userId * - * @param userId + * @param updateUserCmd * @return UserAccount object */ UserAccount updateUser(UpdateUserCmd updateUserCmd); @@ -150,7 +158,7 @@ public interface RegionManager { /** * Disables a user by userId * - * @param userId + * @param id * - the userId * @return UserAccount object */ diff --git a/server/src/org/apache/cloudstack/region/RegionManagerImpl.java b/server/src/org/apache/cloudstack/region/RegionManagerImpl.java index 89107141966..920c068beda 100755 --- a/server/src/org/apache/cloudstack/region/RegionManagerImpl.java +++ b/server/src/org/apache/cloudstack/region/RegionManagerImpl.java @@ -25,6 +25,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -226,6 +227,14 @@ public class RegionManagerImpl extends ManagerBase implements RegionManager, Man return _accountMgr.deleteUser(cmd); } + /** + * {@inheritDoc} + */ + @Override + public boolean moveUser(MoveUserCmd cmd) { + return _accountMgr.moveUser(cmd); + } + /** * {@inheritDoc} */ diff --git a/server/src/org/apache/cloudstack/region/RegionServiceImpl.java b/server/src/org/apache/cloudstack/region/RegionServiceImpl.java index 98cf5005f89..019e5517605 100755 --- a/server/src/org/apache/cloudstack/region/RegionServiceImpl.java +++ b/server/src/org/apache/cloudstack/region/RegionServiceImpl.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; import org.apache.cloudstack.api.command.admin.user.DisableUserCmd; import org.apache.cloudstack.api.command.admin.user.EnableUserCmd; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import org.apache.cloudstack.api.command.user.region.ListRegionsCmd; @@ -153,6 +154,14 @@ public class RegionServiceImpl extends ManagerBase implements RegionService, Man return _regionMgr.deleteUser(cmd); } + /** + * {@inheritDoc} + */ + @Override + public boolean moveUser(MoveUserCmd cmd) { + return _regionMgr.moveUser(cmd); + } + /** * {@inheritDoc} */ diff --git a/server/test/com/cloud/user/MockAccountManagerImpl.java b/server/test/com/cloud/user/MockAccountManagerImpl.java index 30aa49c67ef..b62f08c8725 100644 --- a/server/test/com/cloud/user/MockAccountManagerImpl.java +++ b/server/test/com/cloud/user/MockAccountManagerImpl.java @@ -22,6 +22,7 @@ import java.util.Map; import javax.ejb.Local; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.springframework.stereotype.Component; import org.apache.cloudstack.acl.ControlledEntity; @@ -119,6 +120,16 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco return false; } + @Override + public boolean moveUser(MoveUserCmd moveUserCmd) { + return false; + } + + @Override + public boolean moveUser(long id, Long domainId, long accountId) { + return false; + } + @Override public boolean isAdmin(Long accountId) { // TODO Auto-generated method stub diff --git a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java index 47601440432..6e1716a1053 100644 --- a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java +++ b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java @@ -63,6 +63,7 @@ import com.cloud.dc.dao.PodVlanDaoImpl; import com.cloud.dc.dao.PodVlanMapDaoImpl; import com.cloud.dc.dao.VlanDaoImpl; import com.cloud.domain.dao.DomainDaoImpl; +import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.event.dao.UsageEventDaoImpl; import com.cloud.host.dao.HostDaoImpl; import com.cloud.host.dao.HostDetailsDaoImpl; @@ -316,6 +317,11 @@ public class return Mockito.mock(AccountDetailsDao.class); } + @Bean + public DomainDetailsDao domainDetailsDao() { + return Mockito.mock(DomainDetailsDao.class); + } + @Bean public DataStoreManager dataStoreManager() { return Mockito.mock(DataStoreManager.class); diff --git a/setup/db/db/schema-452to453-cleanup.sql b/setup/db/db/schema-452to453-cleanup.sql index 7e3d6227505..df5ec494ced 100644 --- a/setup/db/db/schema-452to453-cleanup.sql +++ b/setup/db/db/schema-452to453-cleanup.sql @@ -253,3 +253,4 @@ CREATE VIEW `cloud`.`user_view` AS `cloud`.`async_job` ON async_job.instance_id = user.id and async_job.instance_type = 'User' and async_job.job_status = 0; + diff --git a/setup/db/db/schema-452to453.sql b/setup/db/db/schema-452to453.sql index 9b580e62860..d66e30adbca 100644 --- a/setup/db/db/schema-452to453.sql +++ b/setup/db/db/schema-452to453.sql @@ -528,3 +528,27 @@ CREATE TABLE IF NOT EXISTS `cloud`.`crl` ( KEY (`serial`), UNIQUE KEY (`serial`, `cn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ldap binding on domain level +CREATE TABLE IF NOT EXISTS `cloud`.`domain_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `domain_id` bigint unsigned NOT NULL COMMENT 'account id', + `name` varchar(255) NOT NULL, + `value` varchar(255) NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_domain_details__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE +)ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`ldap_trust_map` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `domain_id` bigint unsigned NOT NULL, + `type` varchar(10) NOT NULL, + `name` varchar(255) NOT NULL, + `account_type` int(1) unsigned NOT NULL, + account_id BIGINT(20) DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_ldap_trust_map__bind_location` (`domain_id`, `account_id`), + CONSTRAINT `fk_ldap_trust_map__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE cloud.ldap_configuration ADD COLUMN domain_id BIGINT(20) DEFAULT NULL; diff --git a/test/integration/smoke/test_accounts.py b/test/integration/smoke/test_accounts.py new file mode 100644 index 00000000000..dffb00aaef4 --- /dev/null +++ b/test/integration/smoke/test_accounts.py @@ -0,0 +1,2225 @@ +# 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. +""" P1 tests for Account +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import (random_gen, + cleanup_resources) +from marvin.cloudstackAPI import * +from marvin.lib.base import (Domain, + Account, + ServiceOffering, + VirtualMachine, + Network, + User, + NATRule, + Template, + PublicIPAddress) +from marvin.lib.common import (get_domain, + get_zone, + get_test_template, + list_accounts, + list_virtual_machines, + list_service_offering, + list_templates, + list_users, + get_builtin_template_info, + wait_for_cleanup) +from nose.plugins.attrib import attr +from marvin.cloudstackException import CloudstackAPIException +import time + +from pyVmomi.VmomiSupport import GetVersionFromVersionUri + + +class Services: + + """Test Account Services + """ + + def __init__(self): + self.services = { + "domain": { + "name": "Domain", + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "fr3sca", + }, + "user": { + "email": "user@test.com", + "firstname": "User", + "lastname": "User", + "username": "User", + # Random characters are appended for unique + # username + "password": "fr3sca", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + # in MHz + "memory": 128, + # In MBs + }, + "virtual_machine": { + "displayname": "Test VM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + # Hypervisor type should be same as + # hypervisor type of cluster + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "template": { + "displaytext": "Public Template", + "name": "Public template", + "ostype": 'CentOS 5.6 (64-bit)', + "url": "", + "hypervisor": '', + "format": '', + "isfeatured": True, + "ispublic": True, + "isextractable": True, + "templatefilter": "self" + }, + "natrule": { + "publicport": 22, + "privateport": 22, + "protocol": 'TCP', + }, + "ostype": 'CentOS 5.6 (64-bit)', + "sleep": 60, + "timeout": 10, + } + + +class TestAccounts(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestAccounts, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.hypervisor + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls._cleanup = [cls.service_offering] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created accounts, domains etc + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_create_account(self): + """Test Create Account and user for that account + """ + + # Validate the following + # 1. Create an Account. Verify the account is created. + # 2. Create User associated with that account. Verify the created user + + # Create an account + account = Account.create( + self.apiclient, + self.services["account"] + ) + self.debug("Created account: %s" % account.name) + self.cleanup.append(account) + list_accounts_response = list_accounts( + self.apiclient, + id=account.id + ) + self.assertEqual( + isinstance(list_accounts_response, list), + True, + "Check list accounts for valid data" + ) + self.assertNotEqual( + len(list_accounts_response), + 0, + "Check List Account response" + ) + + account_response = list_accounts_response[0] + self.assertEqual( + account.accounttype, + account_response.accounttype, + "Check Account Type of Created account" + ) + self.assertEqual( + account.name, + account_response.name, + "Check Account Name of Created account" + ) + # Create an User associated with account + user = User.create( + self.apiclient, + self.services["user"], + account=account.name, + domainid=account.domainid + ) + self.debug("Created user: %s" % user.id) + list_users_response = list_users( + self.apiclient, + id=user.id + ) + self.assertEqual( + isinstance(list_users_response, list), + True, + "Check list users for valid data" + ) + + self.assertNotEqual( + len(list_users_response), + 0, + "Check List User response" + ) + + user_response = list_users_response[0] + self.assertEqual( + user.username, + user_response.username, + "Check username of Created user" + ) + self.assertEqual( + user.state, + user_response.state, + "Check state of created user" + ) + self.assertEqual( + "native", + user_response.usersource, + "Check user source of created user" + ) + return + + +class TestRemoveUserFromAccount(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super( + TestRemoveUserFromAccount, + cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.hypervisor + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + # Create an account + cls.account = Account.create( + cls.api_client, + cls.services["account"] + ) + + cls._cleanup = [cls.account, + cls.service_offering, + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created instance, users etc + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_user_remove_VM_running(self): + """Test Remove one user from the account + """ + + # Validate the following + # 1. Create an account with 2 users. + # 2. Start 2 VMs; one for each user of the account + # 3. Remove one user from the account. Verify that account + # still exists. + # 4. Verify that VM started by the removed user are still running + + # Create an User associated with account and VMs + user_1 = User.create( + self.apiclient, + self.services["user"], + account=self.account.name, + domainid=self.account.domainid + ) + self.debug("Created user: %s" % user_1.id) + + user_2 = User.create( + self.apiclient, + self.services["user"], + account=self.account.name, + domainid=self.account.domainid + ) + self.debug("Created user: %s" % user_2.id) + self.cleanup.append(user_2) + + vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id + ) + self.debug("Deployed VM in account: %s, ID: %s" % ( + self.account.name, + vm_1.id + )) + self.cleanup.append(vm_1) + + vm_2 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id + ) + self.debug("Deployed VM in account: %s, ID: %s" % ( + self.account.name, + vm_2.id + )) + self.cleanup.append(vm_2) + + # Remove one of the user + self.debug("Deleting user: %s" % user_1.id) + user_1.delete(self.apiclient) + + # Account should exist after deleting user + accounts_response = list_accounts( + self.apiclient, + id=self.account.id + ) + self.assertEqual( + isinstance(accounts_response, list), + True, + "Check for valid list accounts response" + ) + + self.assertNotEqual( + len(accounts_response), + 0, + "Check List Account response" + ) + vm_response = list_virtual_machines( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(vm_response, list), + True, + "Check for valid list VM response" + ) + + self.assertNotEqual( + len(vm_response), + 0, + "Check List VM response" + ) + + # VMs associated with that account should be running + for vm in vm_response: + self.assertEqual( + vm.state, + 'Running', + "Check state of VMs associated with account" + ) + return + + +class TestNonRootAdminsPrivileges(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super( + TestNonRootAdminsPrivileges, + cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + # Create an account, domain etc + cls.domain = Domain.create( + cls.api_client, + cls.services["domain"], + ) + cls.account = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup = [ + cls.account, + cls.domain + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created accounts + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_non_root_admin_Privileges(self): + """Test to verify Non Root admin previleges""" + + # Validate the following + # 1. Create few accounts/users in ROOT domain + # 2. Verify listAccounts API gives only accounts associated with new + # domain. + + # Create accounts for ROOT domain + account_1 = Account.create( + self.apiclient, + self.services["account"] + ) + self.debug("Created account: %s" % account_1.name) + self.cleanup.append(account_1) + account_2 = Account.create( + self.apiclient, + self.services["account"] + ) + self.debug("Created account: %s" % account_2.name) + self.cleanup.append(account_2) + + accounts_response = list_accounts( + self.apiclient, + domainid=self.domain.id, + listall=True + ) + + self.assertEqual( + isinstance(accounts_response, list), + True, + "Check list accounts response for valid data" + ) + + self.assertEqual( + len(accounts_response), + 1, + "Check List accounts response" + ) + # Verify only account associated with domain is listed + for account in accounts_response: + self.assertEqual( + account.domainid, + self.domain.id, + "Check domain ID of account" + ) + return + + +class TestServiceOfferingSiblings(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super( + TestServiceOfferingSiblings, + cls + ).getClsTestClient().getApiClient() + cls.services = Services().services + + # Create Domains, accounts etc + cls.domain_1 = Domain.create( + cls.api_client, + cls.services["domain"] + ) + cls.domain_2 = Domain.create( + cls.api_client, + cls.services["domain"] + ) + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"], + domainid=cls.domain_1.id + ) + # Create account for doamin_1 + cls.account_1 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_1.id + ) + + # Create an account for domain_2 + cls.account_2 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_2.id + ) + + cls._cleanup = [ + cls.account_1, + cls.account_2, + cls.service_offering, + cls.domain_1, + cls.domain_2, + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created domains, accounts + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_service_offering_siblings(self): + """Test to verify service offerings at same level in hierarchy""" + + # Validate the following + # 1. Verify service offering is visible for domain_1 + # 2. Verify service offering is not visible for domain_2 + + service_offerings = list_service_offering( + self.apiclient, + domainid=self.domain_1.id + ) + self.assertEqual( + isinstance(service_offerings, list), + True, + "Check if valid list service offerings response" + ) + + self.assertNotEqual( + len(service_offerings), + 0, + "Check List Service Offerings response" + ) + + for service_offering in service_offerings: + self.debug("Validating service offering: %s" % service_offering.id) + self.assertEqual( + service_offering.id, + self.service_offering.id, + "Check Service offering ID for domain" + + str(self.domain_1.name) + ) + # Verify private service offering is not visible to other domain + service_offerings = list_service_offering( + self.apiclient, + domainid=self.domain_2.id + ) + self.assertEqual( + service_offerings, + None, + "Check List Service Offerings response for other domain" + ) + return + + +class TestServiceOfferingHierarchy(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super( + TestServiceOfferingHierarchy, + cls + ).getClsTestClient().getApiClient() + cls.services = Services().services + + # Create domain, service offerings etc + cls.domain_1 = Domain.create( + cls.api_client, + cls.services["domain"] + ) + cls.domain_2 = Domain.create( + cls.api_client, + cls.services["domain"], + parentdomainid=cls.domain_1.id + ) + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"], + domainid=cls.domain_1.id + ) + # Create account for doamin_1 + cls.account_1 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_1.id + ) + + # Create an account for domain_2 + cls.account_2 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_2.id + ) + + cls._cleanup = [ + cls.account_2, + cls.domain_2, + cls.service_offering, + cls.account_1, + cls.domain_1, + ] + + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_service_offering_hierarchy(self): + """Test to verify service offerings at same level in hierarchy""" + + # Validate the following + # 1. Verify service offering is visible for domain_1 + # 2. Verify service offering is also visible for domain_2 + + service_offerings = list_service_offering( + self.apiclient, + domainid=self.domain_1.id + ) + self.assertEqual( + isinstance(service_offerings, list), + True, + "Check List Service Offerings for a valid response" + ) + self.assertNotEqual( + len(service_offerings), + 0, + "Check List Service Offerings response" + ) + + for service_offering in service_offerings: + self.assertEqual( + service_offering.id, + self.service_offering.id, + "Check Service offering ID for domain" + + str(self.domain_1.name) + ) + + # Verify private service offering is not visible to other domain + service_offerings = list_service_offering( + self.apiclient, + domainid=self.domain_2.id + ) + self.assertEqual( + service_offerings, + None, + "Check List Service Offerings for a valid response" + ) + return + + +class TestTemplateHierarchy(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestTemplateHierarchy, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.hypervisor = cls.testClient.getHypervisorInfo() + + cls.services = Services().services + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + + # Create domains, accounts and template + cls.domain_1 = Domain.create( + cls.api_client, + cls.services["domain"] + ) + cls.domain_2 = Domain.create( + cls.api_client, + cls.services["domain"], + parentdomainid=cls.domain_1.id + ) + + # Create account for doamin_1 + cls.account_1 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_1.id + ) + + # Create an account for domain_2 + cls.account_2 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain_2.id + ) + + cls._cleanup = [ + cls.account_2, + cls.domain_2, + cls.account_1, + cls.domain_1, + ] + + builtin_info = get_builtin_template_info(cls.api_client, cls.zone.id) + cls.services["template"]["url"] = builtin_info[0] + cls.services["template"]["hypervisor"] = builtin_info[1] + cls.services["template"]["format"] = builtin_info[2] + + # Register new template + cls.template = Template.register( + cls.api_client, + cls.services["template"], + zoneid=cls.zone.id, + account=cls.account_1.name, + domainid=cls.domain_1.id, + hypervisor=cls.hypervisor + ) + + # Wait for template to download + cls.template.download(cls.api_client) + + # Wait for template status to be changed across + time.sleep(60) + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], required_hardware="true") + def test_01_template_hierarchy(self): + """Test to verify template at same level in hierarchy""" + + # Validate the following + # 1. Verify template is visible for domain_1 + # 2. Verify template is also visible for domain_2 + + # Sleep to ensure that template state is reflected across + + templates = list_templates( + self.apiclient, + templatefilter='self', + account=self.account_1.name, + domainid=self.domain_1.id + ) + self.assertEqual( + isinstance(templates, list), + True, + "Template response %s is not a list" % templates + ) + self.assertNotEqual( + len(templates), + 0, + "No templates found" + ) + + for template in templates: + self.assertEqual( + template.id, + self.template.id, + "Check Template ID for domain" + str(self.domain_1.name) + ) + + # Verify private service offering is not visible to other domain + templates = list_templates( + self.apiclient, + id=self.template.id, + templatefilter='all', + account=self.account_2.name, + domainid=self.domain_2.id + ) + self.assertEqual( + isinstance(templates, list), + True, + "Template response %s is not a list" % templates + ) + self.assertNotEqual( + len(templates), + 0, + "No templates found" + ) + + for template in templates: + self.assertEqual( + template.id, + self.template.id, + "Check Template ID for domain" + str(self.domain_2.name) + ) + return + + +class TestAddVmToSubDomain(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestAddVmToSubDomain, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + cls.sub_domain = Domain.create( + cls.api_client, + cls.services["domain"], + parentdomainid=cls.domain.id + ) + + # Create account for doamin_1 + cls.account_1 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + + # Create an account for domain_2 + cls.account_2 = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.sub_domain.id + ) + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"], + domainid=cls.domain.id + ) + + cls._cleanup = [ + cls.account_2, + cls.account_1, + cls.sub_domain, + cls.service_offering + ] + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.hypervisor + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.vm_1 = VirtualMachine.create( + cls.api_client, + cls.services["virtual_machine"], + templateid=cls.template.id, + accountid=cls.account_1.name, + domainid=cls.account_1.domainid, + serviceofferingid=cls.service_offering.id + ) + + cls.vm_2 = VirtualMachine.create( + cls.api_client, + cls.services["virtual_machine"], + templateid=cls.template.id, + accountid=cls.account_2.name, + domainid=cls.account_2.domainid, + serviceofferingid=cls.service_offering.id + ) + return + + @classmethod + def tearDownClass(cls): + try: + # Clean up, terminate the created resources + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created resources + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "advancedns", + "sg"], + required_hardware="false") + def test_01_add_vm_to_subdomain(self): + """ Test Sub domain allowed to launch VM when a Domain + level zone is created""" + + # Validate the following + # 1. Verify VM created by Account_1 is in Running state + # 2. Verify VM created by Account_2 is in Running state + + vm_response = list_virtual_machines( + self.apiclient, + id=self.vm_1.id + ) + self.assertEqual( + isinstance(vm_response, list), + True, + "Check List VM for a valid response" + ) + self.assertNotEqual( + len(vm_response), + 0, + "Check List Template response" + ) + + for vm in vm_response: + self.debug("VM ID: %s and state: %s" % (vm.id, vm.state)) + self.assertEqual( + vm.state, + 'Running', + "Check State of Virtual machine" + ) + + vm_response = list_virtual_machines( + self.apiclient, + id=self.vm_2.id + ) + self.assertNotEqual( + len(vm_response), + 0, + "Check List Template response" + ) + + for vm in vm_response: + self.debug("VM ID: %s and state: %s" % (vm.id, vm.state)) + self.assertEqual( + vm.state, + 'Running', + "Check State of Virtual machine" + ) + return + + +class TestUserDetails(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestUserDetails, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created network offerings + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=[ + "role", + "accounts", + "simulator", + "advanced", + "advancedns", + "basic", + "eip", + "sg" + ]) + def test_updateUserDetails(self): + """Test user update API + """ + + # Steps for test scenario + # 1. create a user account + # 2. update the user details (firstname, lastname, user) with + # updateUser API + # 3. listUsers in the account + # 4. delete the account + # Validate the following + # 1. listAccounts should show account created successfully + # 2. updateUser API should return valid response + # 3. user should be updated with new details + + self.debug("Creating an user account..") + self.account = Account.create( + self.apiclient, + self.services["account"], + domainid=self.domain.id + ) + self.cleanup.append(self.account) + + # Fetching the user details of account + self.debug( + "Fetching user details for account: %s" % + self.account.name) + users = User.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.debug("Updating the details of user: %s" % user_1.name) + firstname = random_gen() + lastname = random_gen() + + self.debug("New firstname: %s, lastname: %s" % (firstname, lastname)) + User.update( + self.apiclient, + user_1.id, + firstname=firstname, + lastname=lastname + ) + + # Fetching the user details of account + self.debug( + "Fetching user details for user: %s" % user_1.name) + users = User.list( + self.apiclient, + id=user_1.id, + listall=True + ) + + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.assertEqual( + user_1.firstname, + firstname, + "User's first name should be updated with new one" + ) + self.assertEqual( + user_1.lastname, + lastname, + "User's last name should be updated with new one" + ) + return + + @attr(tags=[ + "role", + "accounts", + "simulator", + "advanced", + "advancedns", + "basic", + "eip", + "sg" + ]) + def test_updateAdminDetails(self): + """Test update admin details + """ + + # Steps for test scenario + # 1. create a admin account + # 2. update the user details (firstname, lastname, user) with + # updateUser API + # 3. listUsers in the account + # 4. delete the account + # Validate the following + # 1. listAccounts should show account created successfully + # 2. updateUser API should return valid response + # 3. user should be updated with new details + + self.debug("Creating a ROOT admin account") + self.account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + ) + self.cleanup.append(self.account) + + # Fetching the user details of account + self.debug( + "Fetching user details for account: %s" % + self.account.name) + users = User.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.debug("Updating the details of user: %s" % user_1.name) + firstname = random_gen() + lastname = random_gen() + + self.debug("New firstname: %s, lastname: %s" % (firstname, lastname)) + User.update( + self.apiclient, + user_1.id, + firstname=firstname, + lastname=lastname + ) + + # Fetching the user details of account + self.debug( + "Fetching user details for user: %s" % user_1.name) + users = User.list( + self.apiclient, + id=user_1.id, + listall=True + ) + + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.assertEqual( + user_1.firstname, + firstname, + "User's first name should be updated with new one" + ) + self.assertEqual( + user_1.lastname, + lastname, + "User's last name should be updated with new one" + ) + return + + @attr(tags=[ + "role", + "accounts", + "simulator", + "advanced", + "advancedns", + "basic", + "eip", + "sg" + ]) + def test_updateDomainAdminDetails(self): + """Test update domain admin details + """ + + # Steps for test scenario + # 2. update the user details (firstname, lastname, user) with + # updateUser API + # 3. listUsers in the account + # 4. delete the account + # Validate the following + # 1. listAccounts should show account created successfully + # 2. updateUser API should return valid response + # 3. user should be updated with new details + + self.debug("Creating a domain admin account") + self.account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(self.account) + + # Fetching the user details of account + self.debug( + "Fetching user details for account: %s" % + self.account.name) + users = User.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.debug("Updating the details of user: %s" % user_1.name) + firstname = random_gen() + lastname = random_gen() + + self.debug("New firstname: %s, lastname: %s" % (firstname, lastname)) + User.update( + self.apiclient, + user_1.id, + firstname=firstname, + lastname=lastname + ) + + # Fetching the user details of account + self.debug( + "Fetching user details for user: %s" % user_1.name) + users = User.list( + self.apiclient, + id=user_1.id, + listall=True + ) + + self.assertEqual( + isinstance(users, list), + True, + "List users should return a valid list for account" + ) + user_1 = users[0] + self.assertEqual( + user_1.firstname, + firstname, + "User's first name should be updated with new one" + ) + self.assertEqual( + user_1.lastname, + lastname, + "User's last name should be updated with new one" + ) + return + + +class TestUserLogin(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestUserLogin, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created network offerings + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["login", "accounts", "simulator", "advanced", + "advancedns", "basic", "eip", "sg"]) + def test_LoginApiUuidResponse(self): + """Test if Login API does not return UUID's + """ + + # Steps for test scenario + # 1. create a user account + # 2. login to the user account with given credentials (loginCmd) + # 3. delete the user account + # Validate the following + # 1. listAccounts should return account created + # 2. loginResponse should have UUID only is response. Assert by + # checking database id is not same as response id + # Login also succeeds with non NULL sessionId in response + + self.debug("Creating an user account..") + self.account = Account.create( + self.apiclient, + self.services["account"], + domainid=self.domain.id + ) + self.cleanup.append(self.account) + + self.debug("Logging into the cloudstack with login API") + respose = User.login( + self.apiclient, + username=self.account.name, + password=self.services["account"]["password"] + ) + + self.debug("Login API response: %s" % respose) + + self.assertNotEqual( + respose.sessionkey, + None, + "Login to the CloudStack should be successful" + + "response shall have non Null key" + ) + return + + @attr(tags=["login", "accounts", "simulator", "advanced", + "advancedns", "basic", "eip", "sg"]) + def test_LoginApiDomain(self): + """Test login API with domain + """ + + # Steps for test scenario + # 1. create a domain + # 2. create user in the domain + # 3. login to the user account above using UUID domain/user + # 4. delete the user account + # Validate the following + # 1. listDomains returns created domain + # 2. listAccounts returns created user + # 3. loginResponse should have UUID only in responses + # Login also succeeds with non NULL sessionId in response + + self.debug("Creating a domain for login with API domain test") + domain = Domain.create( + self.apiclient, + self.services["domain"], + parentdomainid=self.domain.id + ) + self.debug("Domain: %s is created succesfully." % domain.name) + self.debug( + "Checking if the created domain is listed in list domains API") + domains = Domain.list(self.apiclient, id=domain.id, listall=True) + + self.assertEqual( + isinstance(domains, list), + True, + "List domains shall return a valid response" + ) + self.debug("Creating an user account in domain: %s" % domain.name) + self.account = Account.create( + self.apiclient, + self.services["account"], + domainid=domain.id + ) + self.cleanup.append(self.account) + + accounts = Account.list( + self.apiclient, + name=self.account.name, + domainid=self.account.domainid, + listall=True + ) + + self.assertEqual( + isinstance(accounts, list), + True, + "List accounts should return a valid response" + ) + + self.debug("Logging into the cloudstack with login API") + respose = User.login( + self.apiclient, + username=self.account.name, + password=self.services["account"]["password"], + domainid=domain.id) + self.debug("Login API response: %s" % respose) + + self.assertNotEqual( + respose.sessionkey, + None, + "Login to the CloudStack should be successful" + + "response shall have non Null key" + ) + return + + +class TestUserAPIKeys(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestUserAPIKeys, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + # Create an account, domain etc + cls.domain = Domain.create( + cls.api_client, + cls.services["domain"], + ) + cls.account = Account.create( + cls.api_client, + cls.services["account"], + admin=False, + domainid=cls.domain.id + ) + cls.domain_2 = Domain.create( + cls.api_client, + cls.services["domain"], + ) + cls.account_2 = Account.create( + cls.api_client, + cls.services["account"], + admin=False, + domainid=cls.domain_2.id + ) + cls._cleanup = [ + cls.account, + cls.domain, + cls.account_2, + cls.domain_2 + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created network offerings + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=[ + "role", + "accounts", + "simulator", + "advanced", + "advancedns", + "basic", + "eip", + "sg" + ]) + def test_user_key_renew_same_account(self): + # Create an User associated with the account + user_1 = User.create( + self.apiclient, + self.services["user"], + account=self.account.name, + domainid=self.domain.id + ) + self.cleanup.append(user_1) + account_response = list_accounts( + self.apiclient, + id=self.account.id + )[0] + self.assertEqual( + hasattr(account_response, 'user'), + True, + "Users are included in account response") + + account_users = account_response.user + self.assertEqual( + isinstance(account_users, list), + True, + "Check account for valid data" + ) + self.assertNotEqual( + len(account_users), + 0, + "Check number of User in Account") + [user] = [u for u in account_users if u.username == user_1.username] + self.assertEqual( + user.apikey, + None, + "Check that the user don't have an API key yet") + + self.debug("Register API keys for user") + userkeys = User.registerUserKeys(self.apiclient, user_1.id) + users = list_accounts( + self.apiclient, + id=self.account.id + )[0].user + [user] = [u for u in users if u.id == user_1.id] + self.assertEqual( + user.apikey, + userkeys.apikey, + "Check User api key") + user.secretkey = self.get_secret_key(user.id) + self.assertEqual( + user.secretkey, + userkeys.secretkey, + "Check User having secret key") + + self.debug("Get test client with user keys") + cs_api = self.testClient.getUserApiClient( + UserName=self.account.name, + DomainName=self.account.domain) + self.debug("Renew API keys for user using current keys") + new_keys = User.registerUserKeys(cs_api, user_1.id) + self.assertNotEqual( + userkeys.apikey, + new_keys.apikey, + "Check API key is different") + new_keys.secretkey = self.get_secret_key(user_1.id) + self.assertNotEqual( + userkeys.secretkey, + new_keys.secretkey, + "Check secret key is different") + + def get_secret_key(self, id): + cmd = getUserKeys.getUserKeysCmd() + cmd.id = id + keypair = self.apiclient.getUserKeys(cmd) + return keypair.secretkey + + @attr(tags=[ + "role", + "accounts", + "simulator", + "advanced", + "advancedns", + "basic", + "eip", + "sg" + ]) + def test_user_cannot_renew_other_keys(self): + cs_api = self.testClient.getUserApiClient( + UserName=self.account.name, + DomainName=self.account.domain) + self.debug("Try to change API key of an account in another domain") + users = list_accounts( + self.apiclient, + id=self.account_2.id + )[0].user + with self.assertRaises(CloudstackAPIException) as e: + User.registerUserKeys(cs_api, users[0].id) + + +class TestDomainForceRemove(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestDomainForceRemove, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.hypervisor + ) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + # Clean up, terminate the created resources + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created resources + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "domains", + "advanced", + "advancedns", + "simulator", + "dvs"], + required_hardware="false") + def test_forceDeleteDomain(self): + """ Test delete domain with force option""" + + # Steps for validations + # 1. create a domain DOM + # 2. create 2 users under this domain + # 3. deploy 1 VM into each of these user accounts + # 4. create PF / FW rules for port 22 on these VMs for their + # respective accounts + # 5. delete the domain with force=true option + # Validate the following + # 1. listDomains should list the created domain + # 2. listAccounts should list the created accounts + # 3. listvirtualmachines should show the Running VMs + # 4. PF and FW rules should be shown in listFirewallRules + # 5. domain should delete successfully and above three list calls + # should show all the resources now deleted. listRouters should + # not return any routers in the deleted accounts/domains + + self.debug("Creating a domain for login with API domain test") + domain = Domain.create( + self.apiclient, + self.services["domain"], + parentdomainid=self.domain.id + ) + self.debug("Domain is created succesfully.") + self.debug( + "Checking if the created domain is listed in list domains API") + domains = Domain.list(self.apiclient, id=domain.id, listall=True) + + self.assertEqual( + isinstance(domains, list), + True, + "List domains shall return a valid response" + ) + self.debug("Creating 2 user accounts in domain: %s" % domain.name) + self.account_1 = Account.create( + self.apiclient, + self.services["account"], + domainid=domain.id + ) + + self.account_2 = Account.create( + self.apiclient, + self.services["account"], + domainid=domain.id + ) + + try: + self.debug("Creating a tiny service offering for VM deployment") + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + domainid=self.domain.id + ) + + self.debug("Deploying virtual machine in account 1: %s" % + self.account_1.name) + vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account_1.name, + domainid=self.account_1.domainid, + serviceofferingid=self.service_offering.id + ) + + self.debug("Deploying virtual machine in account 2: %s" % + self.account_2.name) + VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account_2.name, + domainid=self.account_2.domainid, + serviceofferingid=self.service_offering.id + ) + + networks = Network.list( + self.apiclient, + account=self.account_1.name, + domainid=self.account_1.domainid, + listall=True + ) + self.assertEqual( + isinstance(networks, list), + True, + "List networks should return a valid response" + ) + network_1 = networks[0] + self.debug("Default network in account 1: %s is %s" % ( + self.account_1.name, + network_1.name)) + src_nat_list = PublicIPAddress.list( + self.apiclient, + associatednetworkid=network_1.id, + account=self.account_1.name, + domainid=self.account_1.domainid, + listall=True, + issourcenat=True, + ) + self.assertEqual( + isinstance(src_nat_list, list), + True, + "List Public IP should return a valid source NAT" + ) + self.assertNotEqual( + len(src_nat_list), + 0, + "Length of response from listPublicIp should not be 0" + ) + + src_nat = src_nat_list[0] + + self.debug( + "Trying to create a port forwarding rule in source NAT: %s" % + src_nat.ipaddress) + # Create NAT rule + nat_rule = NATRule.create( + self.apiclient, + vm_1, + self.services["natrule"], + ipaddressid=src_nat.id + ) + self.debug("Created PF rule on source NAT: %s" % src_nat.ipaddress) + + nat_rules = NATRule.list(self.apiclient, id=nat_rule.id) + + self.assertEqual( + isinstance(nat_rules, list), + True, + "List NAT should return a valid port forwarding rules" + ) + + self.assertNotEqual( + len(nat_rules), + 0, + "Length of response from listLbRules should not be 0" + ) + except Exception as e: + self.clenaup.append(self.account_1) + self.cleanup.append(self.account_2) + self.fail(e) + + self.debug("Deleting domain with force option") + try: + domain.delete(self.apiclient, cleanup=True) + except Exception as e: + self.debug("Waiting for account.cleanup.interval" + + " to cleanup any remaining resouces") + # Sleep 3*account.gc to ensure that all resources are deleted + wait_for_cleanup(self.apiclient, ["account.cleanup.interval"] * 3) + with self.assertRaises(CloudstackAPIException): + Domain.list( + self.apiclient, + id=domain.id, + listall=True + ) + + self.debug("Checking if the resources in domain are deleted") + with self.assertRaises(CloudstackAPIException): + Account.list( + self.apiclient, + name=self.account_1.name, + domainid=self.account_1.domainid, + listall=True + ) + return + + @attr( + tags=[ + "domains", + "advanced", + "advancedns", + "simulator"], + required_hardware="false") + def test_DeleteDomain(self): + """ Test delete domain without force option""" + + # Steps for validations + # 1. create a domain DOM + # 2. create 2 users under this domain + # 3. deploy 1 VM into each of these user accounts + # 4. create PF / FW rules for port 22 on these VMs for their + # respective accounts + # 5. delete the domain with force=false option + # Validate the following + # 1. listDomains should list the created domain + # 2. listAccounts should list the created accounts + # 3. listvirtualmachines should show the Running VMs + # 4. PF and FW rules should be shown in listFirewallRules + # 5. domain deletion should fail saying there are resources under use + + self.debug("Creating a domain for login with API domain test") + domain = Domain.create( + self.apiclient, + self.services["domain"], + parentdomainid=self.domain.id + ) + self.debug("Domain: %s is created successfully." % domain.name) + self.debug( + "Checking if the created domain is listed in list domains API") + domains = Domain.list(self.apiclient, id=domain.id, listall=True) + + self.assertEqual( + isinstance(domains, list), + True, + "List domains shall return a valid response" + ) + self.debug("Creating 2 user accounts in domain: %s" % domain.name) + self.account_1 = Account.create( + self.apiclient, + self.services["account"], + domainid=domain.id + ) + self.cleanup.append(self.account_1) + + self.account_2 = Account.create( + self.apiclient, + self.services["account"], + domainid=domain.id + ) + self.cleanup.append(self.account_2) + + self.debug("Creating a tiny service offering for VM deployment") + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + domainid=self.domain.id + ) + self.cleanup.append(self.service_offering) + + self.debug("Deploying virtual machine in account 1: %s" % + self.account_1.name) + vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account_1.name, + domainid=self.account_1.domainid, + serviceofferingid=self.service_offering.id + ) + + self.debug("Deploying virtual machine in account 2: %s" % + self.account_2.name) + VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account_2.name, + domainid=self.account_2.domainid, + serviceofferingid=self.service_offering.id + ) + + networks = Network.list( + self.apiclient, + account=self.account_1.name, + domainid=self.account_1.domainid, + listall=True + ) + self.assertEqual( + isinstance(networks, list), + True, + "List networks should return a valid response" + ) + network_1 = networks[0] + self.debug("Default network in account 1: %s is %s" % ( + self.account_1.name, + network_1.name)) + src_nat_list = PublicIPAddress.list( + self.apiclient, + associatednetworkid=network_1.id, + account=self.account_1.name, + domainid=self.account_1.domainid, + listall=True, + issourcenat=True, + ) + self.assertEqual( + isinstance(src_nat_list, list), + True, + "List Public IP should return a valid source NAT" + ) + self.assertNotEqual( + len(src_nat_list), + 0, + "Length of response from listPublicIp should not be 0" + ) + + src_nat = src_nat_list[0] + + self.debug( + "Trying to create a port forwarding rule in source NAT: %s" % + src_nat.ipaddress) + # Create NAT rule + nat_rule = NATRule.create( + self.apiclient, + vm_1, + self.services["natrule"], + ipaddressid=src_nat.id + ) + self.debug("Created PF rule on source NAT: %s" % src_nat.ipaddress) + + nat_rules = NATRule.list(self.apiclient, id=nat_rule.id) + + self.assertEqual( + isinstance(nat_rules, list), + True, + "List NAT should return a valid port forwarding rules" + ) + + self.assertNotEqual( + len(nat_rules), + 0, + "Length of response from listLbRules should not be 0" + ) + + self.debug("Deleting domain without force option") + with self.assertRaises(Exception): + domain.delete(self.apiclient, cleanup=False) + return + +class TestMoveUser(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestMoveUser, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.testdata = cls.testClient.getParsedTestDataConfig() + + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.testdata['mode'] = cls.zone.networktype + + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.testdata["ostype"] + ) + + cls.testdata["virtual_machine"]["zoneid"] = cls.zone.id + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + # Clean up, terminate the created resources + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + self.testdata = self.testClient.getParsedTestDataConfig() + self.account1 = Account.create( + self.apiclient, + self.testdata["acl"]["accountD1"], + domainid=self.domain.id + ) + self.cleanup.append(self.account1) + + self.account2 = Account.create( + self.apiclient, + self.testdata["acl"]["accountD1A"], + domainid=self.domain.id + ) + self.cleanup.append(self.account2) + + self.user = User.create( + self.apiclient, + self.testdata["user"], + account=self.account1.name, + domainid=self.account1.domainid + ) + + return + + def tearDown(self): + try: + # Clean up, terminate the created resources + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false") + def test_move_user_to_accountID(self): + + self.user.move(self.api_client, dest_accountid=self.account2.id) + + self.assertEqual( + self.account2.name, + self.user.list(self.apiclient, id=self.user.id)[0].account, + "Check user source of created user" + ) + return + + @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false") + def test_move_user_to_account_name(self): + + self.user.move(self.api_client, dest_account=self.account2.name) + + self.assertEqual( + self.account2.name, + self.user.list(self.apiclient, id=self.user.id)[0].account, + "Check user source of created user" + ) + return + + @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false") + def test_move_user_to_different_domain(self): + domain2 = Domain.create(self.api_client, + self.testdata["domain"], + parentdomainid=self.domain.id + ) + self.cleanup.append(domain2) + + account_different_domain = Account.create( + self.apiclient, + self.testdata["acl"]["accountD1B"], + domainid=domain2.id + ) + self.cleanup.append(account_different_domain) + try: + self.user.move(self.api_client, dest_account=account_different_domain.name) + except Exception: + pass + else: + self.fail("It should not be allowed to move users across accounts in different domains, failing") + + account_different_domain.delete(self.api_client) + return + + @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false") + def test_move_user_incorrect_account_id(self): + + try: + self.user.move(self.api_client, dest_accountid='incorrect-account-id') + except Exception: + pass + else: + self.fail("moving to non-existing account should not be possible, failing") + return + + @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false") + def test_move_user_incorrect_account_name(self): + + try: + self.user.move(self.api_client, dest_account='incorrect-account-name') + except Exception: + pass + else: + self.fail("moving to non-existing account should not be possible, failing") + return diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py index 75609dd1e47..e12bad7fa69 100644 --- a/tools/marvin/marvin/config/test_data.py +++ b/tools/marvin/marvin/config/test_data.py @@ -70,6 +70,15 @@ test_data = { "url": 'http://10.147.60.15/42xescauto spaces/42xesc Clusters', "clustername": 'VMWare Cluster with Space in DC name', }, + "user": { + "email": "user@test.com", + "firstname": "User", + "lastname": "User", + "username": "User", + # Random characters are appended for unique + # username + "password": "fr3sca", + }, "small": { "displayname": "testserver", "username": "root", diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 63f07232a43..a9c84be6a9e 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -268,6 +268,19 @@ class User: cmd.id = self.id apiclient.deleteUser(cmd) + def move(self, api_client, dest_accountid = None, dest_account = None, domain= None): + + if all([dest_account, dest_accountid]) is None: + raise Exception("Please add either destination account or destination account ID.") + + cmd = moveUser.moveUserCmd() + cmd.id = self.id + cmd.accountid = dest_accountid + cmd.account = dest_account + cmd.domain = domain + + return api_client.moveUser(cmd) + @classmethod def list(cls, apiclient, **kwargs): """Lists users and provides detailed account information for diff --git a/ui/scripts/domains.js b/ui/scripts/domains.js index e46f104364d..7872dbd32b0 100644 --- a/ui/scripts/domains.js +++ b/ui/scripts/domains.js @@ -341,6 +341,15 @@ } } }, + + tabFilter: function(args) { + var hiddenTabs = []; + if(!isAdmin()) { + hiddenTabs.push('settings'); + } + return hiddenTabs; + }, + tabs: { details: { title: 'label.details', @@ -522,36 +531,6 @@ domainObj["vmTotal"] = totalVMs; domainObj["volumeTotal"] = totalVolumes; - /* $.ajax({ - url: createURL("listVirtualMachines&details=min&domainid=" + domainObj.id), - async: false, - dataType: "json", - success: function(json) { - var items = json.listvirtualmachinesresponse.virtualmachine; - var total; - if (items != null) - total = items.length; - else - total = 0; - domainObj["vmTotal"] = total; - } - }); - - $.ajax({ - url: createURL("listVolumes&domainid=" + domainObj.id), - async: false, - dataType: "json", - success: function(json) { - var items = json.listvolumesresponse.volume; - var total; - if (items != null) - total = items.length; - else - total = 0; - domainObj["volumeTotal"] = total; - } - });*/ - $.ajax({ url: createURL("listResourceLimits&domainid=" + domainObj.id), async: false, @@ -606,7 +585,58 @@ actionFilter: domainActionfilter }); } + }, + // Granular settings for domains + settings: { + title: 'label.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + $.ajax({ + url: createURL('listConfigurations&domainid=' + args.context.domains[0].id), + data: listViewDataProvider(args, {}, { searchBy: 'name' }), + success: function(json) { + args.response.success({ + data: json.listconfigurationsresponse.configuration + }); + + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + + } + }); + + }, + actions: { + edit: function(args) { + // call updateDomainLevelParameters + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; + + $.ajax({ + url: createURL('updateConfiguration&domainid=' + args.context.domains[0].id), + data: data, + success: function(json) { + var item = json.updateconfigurationresponse.configuration; + args.response.success({ + data: item + }); + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + + }); + + } + } + }) } + } }, labelField: 'name',