This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 85750f918ba9134fc4f34d0153796bca94325727 Merge: 9776157 39471c8 Author: Rohit Yadav <[email protected]> AuthorDate: Wed Jun 20 12:31:52 2018 +0530 Merge branch '4.11' Signed-off-by: Rohit Yadav <[email protected]> .../apache/cloudstack/storage/configdrive/ConfigDrive.java | 14 +++++++++++--- .../cloudstack/storage/configdrive/ConfigDriveBuilder.java | 5 +++++ .../cloudstack/storage/configdrive/ConfigDriveTest.java | 2 +- packaging/centos63/cloud.spec | 1 + packaging/centos7/cloud.spec | 1 + .../wrapper/LibvirtHandleConfigDriveCommandWrapper.java | 3 +-- .../cloud/network/element/ConfigDriveNetworkElement.java | 3 ++- .../src/main/java/com/cloud/user/AccountManagerImpl.java | 2 ++ .../storage/resource/NfsSecondaryStorageResource.java | 7 ++++--- systemvm/debian/opt/cloud/bin/configure.py | 9 +++------ systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 7 ++++--- .../systemvmtemplate/scripts/install_systemvm_packages.sh | 6 ++++-- 12 files changed, 39 insertions(+), 21 deletions(-) diff --cc server/src/main/java/com/cloud/user/AccountManagerImpl.java index e9762b1,0000000..6025818 mode 100644,000000..100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@@ -1,2853 -1,0 +1,2855 @@@ +// 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.user; + +import java.net.InetAddress; +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.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.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.QuerySelector; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +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.GetUserKeysCmd; +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.config.ApiServiceConfiguration; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao; +import org.apache.cloudstack.utils.baremetal.BaremetalUtils; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.api.ApiDBUtils; +import com.cloud.api.query.vo.ControlledViewEntity; +import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource.ResourceOwnerType; +import com.cloud.configuration.ResourceCountVO; +import com.cloud.configuration.ResourceLimit; +import com.cloud.configuration.dao.ResourceCountDao; +import com.cloud.configuration.dao.ResourceLimitDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterVnetDao; +import com.cloud.dc.dao.DedicatedResourceDao; +import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.ActionEvents; +import com.cloud.event.EventTypes; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.VpnUserVO; +import com.cloud.network.as.AutoScaleManager; +import com.cloud.network.dao.AccountGuestVlanMapDao; +import com.cloud.network.dao.AccountGuestVlanMapVO; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.dao.VpnUserDao; +import com.cloud.network.security.SecurityGroupManager; +import com.cloud.network.security.dao.SecurityGroupDao; +import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.network.vpn.Site2SiteVpnManager; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; +import com.cloud.projects.Project; +import com.cloud.projects.Project.ListProjectResourcesCriteria; +import com.cloud.projects.ProjectInvitationVO; +import com.cloud.projects.ProjectManager; +import com.cloud.projects.ProjectVO; +import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.region.ha.GlobalLoadBalancingRulesService; +import com.cloud.server.auth.UserAuthenticator; +import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeApiService; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotManager; +import com.cloud.template.TemplateManager; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account.State; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; +import com.cloud.user.dao.UserAccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.ConstantTimeComparator; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.InstanceGroupVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.ReservationContextImpl; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.InstanceGroupDao; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager { + public static final Logger s_logger = Logger.getLogger(AccountManagerImpl.class); + + @Inject + private AccountDao _accountDao; + @Inject + private ConfigurationDao _configDao; + @Inject + private ResourceCountDao _resourceCountDao; + @Inject + private UserDao _userDao; + @Inject + private InstanceGroupDao _vmGroupDao; + @Inject + private UserAccountDao _userAccountDao; + @Inject + private VolumeDao _volumeDao; + @Inject + private UserVmDao _userVmDao; + @Inject + private VMTemplateDao _templateDao; + @Inject + private NetworkDao _networkDao; + @Inject + private SecurityGroupDao _securityGroupDao; + @Inject + private VMInstanceDao _vmDao; + @Inject + private SecurityGroupManager _networkGroupMgr; + @Inject + private NetworkOrchestrationService _networkMgr; + @Inject + private SnapshotManager _snapMgr; + @Inject + private VMSnapshotManager _vmSnapshotMgr; + @Inject + private VMSnapshotDao _vmSnapshotDao; + @Inject + private UserVmManager _vmMgr; + @Inject + private TemplateManager _tmpltMgr; + @Inject + private ConfigurationManager _configMgr; + @Inject + private VirtualMachineManager _itMgr; + @Inject + private RemoteAccessVpnDao _remoteAccessVpnDao; + @Inject + private RemoteAccessVpnService _remoteAccessVpnMgr; + @Inject + private VpnUserDao _vpnUser; + @Inject + private DataCenterDao _dcDao; + @Inject + private DomainManager _domainMgr; + @Inject + private ProjectManager _projectMgr; + @Inject + private ProjectDao _projectDao; + @Inject + private AccountDetailsDao _accountDetailsDao; + @Inject + private DomainDao _domainDao; + @Inject + private ProjectAccountDao _projectAccountDao; + @Inject + private IPAddressDao _ipAddressDao; + @Inject + private VpcManager _vpcMgr; + @Inject + private Site2SiteVpnManager _vpnMgr; + @Inject + private AutoScaleManager _autoscaleMgr; + @Inject + private VolumeApiService volumeService; + @Inject + private AffinityGroupDao _affinityGroupDao; + @Inject + private AccountGuestVlanMapDao _accountGuestVlanMapDao; + @Inject + private DataCenterVnetDao _dataCenterVnetDao; + @Inject + private ResourceLimitService _resourceLimitMgr; + @Inject + private ResourceLimitDao _resourceLimitDao; + @Inject + private DedicatedResourceDao _dedicatedDao; + @Inject + private GlobalLoadBalancerRuleDao _gslbRuleDao; + @Inject + private SSHKeyPairDao _sshKeyPairDao; + + private List<QuerySelector> _querySelectors; + + @Inject + private MessageBus _messageBus; + + @Inject + private GlobalLoadBalancingRulesService _gslbService; + + private List<UserAuthenticator> _userAuthenticators; + protected List<UserAuthenticator> _userPasswordEncoders; + + @Inject + private IpAddressManager _ipAddrMgr; + + private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AccountChecker")); + + private int _allowedLoginAttempts; + + private UserVO _systemUser; + private AccountVO _systemAccount; + + private List<SecurityChecker> _securityCheckers; + private int _cleanupInterval; + + public List<UserAuthenticator> getUserAuthenticators() { + return _userAuthenticators; + } + + public void setUserAuthenticators(List<UserAuthenticator> authenticators) { + _userAuthenticators = authenticators; + } + + public List<UserAuthenticator> getUserPasswordEncoders() { + return _userPasswordEncoders; + } + + public void setUserPasswordEncoders(List<UserAuthenticator> encoders) { + _userPasswordEncoders = encoders; + } + + public List<SecurityChecker> getSecurityCheckers() { + return _securityCheckers; + } + + public void setSecurityCheckers(List<SecurityChecker> securityCheckers) { + _securityCheckers = securityCheckers; + } + + public List<QuerySelector> getQuerySelectors() { + return _querySelectors; + } + + public void setQuerySelectors(List<QuerySelector> querySelectors) { + _querySelectors = querySelectors; + } + + @Override + public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { + _systemAccount = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM); + if (_systemAccount == null) { + throw new ConfigurationException("Unable to find the system account using " + Account.ACCOUNT_ID_SYSTEM); + } + + _systemUser = _userDao.findById(User.UID_SYSTEM); + if (_systemUser == null) { + throw new ConfigurationException("Unable to find the system user using " + User.UID_SYSTEM); + } + + Map<String, String> configs = _configDao.getConfiguration(params); + + String loginAttempts = configs.get(Config.IncorrectLoginAttemptsAllowed.key()); + _allowedLoginAttempts = NumbersUtil.parseInt(loginAttempts, 5); + + String value = configs.get(Config.AccountCleanupInterval.key()); + _cleanupInterval = NumbersUtil.parseInt(value, 60 * 60 * 24); // 1 day. + + return true; + } + + @Override + public UserVO getSystemUser() { + if (_systemUser == null) { + _systemUser = _userDao.findById(User.UID_SYSTEM); + } + return _systemUser; + } + + @Override + public boolean start() { + _executor.scheduleAtFixedRate(new AccountCleanupTask(), _cleanupInterval, _cleanupInterval, TimeUnit.SECONDS); + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public AccountVO getSystemAccount() { + if (_systemAccount == null) { + _systemAccount = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM); + } + return _systemAccount; + } + + @Override + public boolean isAdmin(Long accountId) { + if (accountId != null) { + AccountVO acct = _accountDao.findById(accountId); + if (acct == null) { + return false; //account is deleted or does not exist + } + if ((isRootAdmin(accountId)) || (isDomainAdmin(accountId)) || (isResourceDomainAdmin(accountId))) { + return true; + } else if (acct.getType() == Account.ACCOUNT_TYPE_READ_ONLY_ADMIN) { + return true; + } + + } + return false; + } + + @Override + public boolean isRootAdmin(Long accountId) { + if (accountId != null) { + AccountVO acct = _accountDao.findById(accountId); + if (acct == null) { + return false; //account is deleted or does not exist + } + for (SecurityChecker checker : _securityCheckers) { + try { + if (checker.checkAccess(acct, null, null, "SystemCapability")) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Root Access granted to " + acct + " by " + checker.getName()); + } + return true; + } + } catch (PermissionDeniedException ex) { + return false; + } + } + } + return false; + } + + @Override + public boolean isDomainAdmin(Long accountId) { + if (accountId != null) { + AccountVO acct = _accountDao.findById(accountId); + if (acct == null) { + return false; //account is deleted or does not exist + } + for (SecurityChecker checker : _securityCheckers) { + try { + if (checker.checkAccess(acct, null, null, "DomainCapability")) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("DomainAdmin Access granted to " + acct + " by " + checker.getName()); + } + return true; + } + } catch (PermissionDeniedException ex) { + return false; + } + } + } + return false; + } + + @Override + public boolean isNormalUser(long accountId) { + AccountVO acct = _accountDao.findById(accountId); + if (acct != null && acct.getType() == Account.ACCOUNT_TYPE_NORMAL) { + return true; + } + return false; + } + + public boolean isResourceDomainAdmin(Long accountId) { + if (accountId != null) { + AccountVO acct = _accountDao.findById(accountId); + if (acct == null) { + return false; //account is deleted or does not exist + } + for (SecurityChecker checker : _securityCheckers) { + try { + if (checker.checkAccess(acct, null, null, "DomainResourceCapability")) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("ResourceDomainAdmin Access granted to " + acct + " by " + checker.getName()); + } + return true; + } + } catch (PermissionDeniedException ex) { + return false; + } + } + } + return false; + } + + public boolean isInternalAccount(long accountId) { + Account account = _accountDao.findById(accountId); + if (account == null) { + return false; //account is deleted or does not exist + } + if (isRootAdmin(accountId) || (account.getType() == Account.ACCOUNT_ID_SYSTEM)) { + return true; + } + return false; + } + + @Override + public void checkAccess(Account caller, Domain domain) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(caller, domain)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + caller + " to " + domain + " by " + checker.getName()); + } + return; + } + } + throw new PermissionDeniedException("There's no way to confirm " + caller + " has access to " + domain); + } + + @Override + public void checkAccess(Account caller, AccessType accessType, boolean sameOwner, ControlledEntity... entities) { + checkAccess(caller, accessType, sameOwner, null, entities); + } + + @Override + public void checkAccess(Account caller, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) { + + //check for the same owner + Long ownerId = null; + ControlledEntity prevEntity = null; + if (sameOwner) { + for (ControlledEntity entity : entities) { + if (sameOwner) { + if (ownerId == null) { + ownerId = entity.getAccountId(); + } else if (ownerId.longValue() != entity.getAccountId()) { + throw new PermissionDeniedException("Entity " + entity + " and entity " + prevEntity + " belong to different accounts"); + } + prevEntity = entity; + } + } + } + + if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || isRootAdmin(caller.getId())) { + // no need to make permission checks if the system/root admin makes the call + if (s_logger.isTraceEnabled()) { + s_logger.trace("No need to make permission check for System/RootAdmin account, returning true"); + } + return; + } + + HashMap<Long, List<ControlledEntity>> domains = new HashMap<Long, List<ControlledEntity>>(); + + for (ControlledEntity entity : entities) { + long domainId = entity.getDomainId(); + if (entity.getAccountId() != -1 && domainId == -1) { // If account exists domainId should too so calculate + // it. This condition might be hit for templates or entities which miss domainId in their tables + Account account = ApiDBUtils.findAccountById(entity.getAccountId()); + domainId = account != null ? account.getDomainId() : -1; + } + if (entity.getAccountId() != -1 && domainId != -1 && !(entity instanceof VirtualMachineTemplate) && !(entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) + && !(entity instanceof AffinityGroup)) { + List<ControlledEntity> toBeChecked = domains.get(entity.getDomainId()); + // for templates, we don't have to do cross domains check + if (toBeChecked == null) { + toBeChecked = new ArrayList<ControlledEntity>(); + domains.put(domainId, toBeChecked); + } + toBeChecked.add(entity); + } + boolean granted = false; + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(caller, entity, accessType, apiName)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access to " + entity + " granted to " + caller + " by " + checker.getName()); + } + granted = true; + break; + } + } + + if (!granted) { + assert false : "How can all of the security checkers pass on checking this check: " + entity; + throw new PermissionDeniedException("There's no way to confirm " + caller + " has access to " + entity); + } + } + + for (Map.Entry<Long, List<ControlledEntity>> domain : domains.entrySet()) { + for (SecurityChecker checker : _securityCheckers) { + Domain d = _domainMgr.getDomain(domain.getKey()); + if (d == null || d.getRemoved() != null) { + throw new PermissionDeniedException("Domain is not found.", caller, domain.getValue()); + } + try { + checker.checkAccess(caller, d); + } catch (PermissionDeniedException e) { + e.addDetails(caller, domain.getValue()); + throw e; + } + } + } + + // check that resources belong to the same account + + } + + @Override + public Long checkAccessAndSpecifyAuthority(Account caller, Long zoneId) { + // We just care for resource domain admin for now. He should be permitted to see only his zone. + if (isResourceDomainAdmin(caller.getAccountId())) { + if (zoneId == null) { + return getZoneIdForAccount(caller); + } else if (zoneId.compareTo(getZoneIdForAccount(caller)) != 0) { + throw new PermissionDeniedException("Caller " + caller + "is not allowed to access the zone " + zoneId); + } else { + return zoneId; + } + } else { + return zoneId; + } + } + + private Long getZoneIdForAccount(Account account) { + + // Currently just for resource domain admin + List<DataCenterVO> dcList = _dcDao.findZonesByDomainId(account.getDomainId()); + if (dcList != null && dcList.size() != 0) { + return dcList.get(0).getId(); + } else { + throw new CloudRuntimeException("Failed to find any private zone for Resource domain admin."); + } + + } + + @DB + public void updateLoginAttempts(final Long id, final int attempts, final boolean toDisable) { + try { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + UserAccountVO user = null; + user = _userAccountDao.lockRow(id, true); + user.setLoginAttempts(attempts); + if (toDisable) { + user.setState(State.disabled.toString()); + } + _userAccountDao.update(id, user); + } + }); + } catch (Exception e) { + s_logger.error("Failed to update login attempts for user with id " + id); + } + } + + private boolean doSetUserStatus(long userId, State state) { + UserVO userForUpdate = _userDao.createForUpdate(); + userForUpdate.setState(state); + return _userDao.update(Long.valueOf(userId), userForUpdate); + } + + @Override + public boolean enableAccount(long accountId) { + boolean success = false; + AccountVO acctForUpdate = _accountDao.createForUpdate(); + acctForUpdate.setState(State.enabled); + acctForUpdate.setNeedsCleanup(false); + success = _accountDao.update(Long.valueOf(accountId), acctForUpdate); + return success; + } + + protected boolean lockAccount(long accountId) { + boolean success = false; + Account account = _accountDao.findById(accountId); + if (account != null) { + if (account.getState().equals(State.locked)) { + return true; // already locked, no-op + } else if (account.getState().equals(State.enabled)) { + AccountVO acctForUpdate = _accountDao.createForUpdate(); + acctForUpdate.setState(State.locked); + success = _accountDao.update(Long.valueOf(accountId), acctForUpdate); + } else { + if (s_logger.isInfoEnabled()) { + s_logger.info("Attempting to lock a non-enabled account, current state is " + account.getState() + " (accountId: " + accountId + "), locking failed."); + } + } + } else { + s_logger.warn("Failed to lock account " + accountId + ", account not found."); + } + return success; + } + + @Override + public boolean deleteAccount(AccountVO account, long callerUserId, Account caller) { + long accountId = account.getId(); + + // delete the account record + if (!_accountDao.remove(accountId)) { + s_logger.error("Unable to delete account " + accountId); + return false; + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Removed account " + accountId); + } + + return cleanupAccount(account, callerUserId, caller); + } + + protected boolean cleanupAccount(AccountVO account, long callerUserId, Account caller) { + long accountId = account.getId(); + boolean accountCleanupNeeded = false; + + try { + // cleanup the users from the account + List<UserVO> users = _userDao.listByAccount(accountId); + for (UserVO user : users) { + if (!_userDao.remove(user.getId())) { + s_logger.error("Unable to delete user: " + user + " as a part of account " + account + " cleanup"); + accountCleanupNeeded = true; + } + } + + // delete global load balancer rules for the account. + List<org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleVO> gslbRules = _gslbRuleDao.listByAccount(accountId); + if (gslbRules != null && !gslbRules.isEmpty()) { + _gslbService.revokeAllGslbRulesForAccount(caller, accountId); + } + + // delete the account from project accounts + _projectAccountDao.removeAccountFromProjects(accountId); + + if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) { + // delete the account from group + _messageBus.publish(_name, MESSAGE_REMOVE_ACCOUNT_EVENT, PublishScope.LOCAL, accountId); + } + + // delete all vm groups belonging to accont + List<InstanceGroupVO> groups = _vmGroupDao.listByAccountId(accountId); + for (InstanceGroupVO group : groups) { + if (!_vmMgr.deleteVmGroup(group.getId())) { + s_logger.error("Unable to delete group: " + group.getId()); + accountCleanupNeeded = true; + } + } + + // Delete the snapshots dir for the account. Have to do this before destroying the VMs. + boolean success = _snapMgr.deleteSnapshotDirsForAccount(accountId); + if (success) { + s_logger.debug("Successfully deleted snapshots directories for all volumes under account " + accountId + " across all zones"); + } + + // clean up templates + List<VMTemplateVO> userTemplates = _templateDao.listByAccountId(accountId); + boolean allTemplatesDeleted = true; + for (VMTemplateVO template : userTemplates) { + if (template.getRemoved() == null) { + try { + allTemplatesDeleted = _tmpltMgr.delete(callerUserId, template.getId(), null); + } catch (Exception e) { + s_logger.warn("Failed to delete template while removing account: " + template.getName() + " due to: ", e); + allTemplatesDeleted = false; + } + } + } + + if (!allTemplatesDeleted) { + s_logger.warn("Failed to delete templates while removing account id=" + accountId); + accountCleanupNeeded = true; + } + + // Destroy VM Snapshots + List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.listByAccountId(Long.valueOf(accountId)); + for (VMSnapshot vmSnapshot : vmSnapshots) { + try { + _vmSnapshotMgr.deleteVMSnapshot(vmSnapshot.getId()); + } catch (Exception e) { + s_logger.debug("Failed to cleanup vm snapshot " + vmSnapshot.getId() + " due to " + e.toString()); + } + } + + // Destroy the account's VMs + List<UserVmVO> vms = _userVmDao.listByAccountId(accountId); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Expunging # of vms (accountId=" + accountId + "): " + vms.size()); + } + + for (UserVmVO vm : vms) { + if (vm.getState() != VirtualMachine.State.Destroyed && vm.getState() != VirtualMachine.State.Expunging) { + try { + _vmMgr.destroyVm(vm.getId(), false); + } catch (Exception e) { + e.printStackTrace(); + s_logger.warn("Failed destroying instance " + vm.getUuid() + " as part of account deletion."); + } + } + // no need to catch exception at this place as expunging vm + // should pass in order to perform further cleanup + if (!_vmMgr.expunge(vm, callerUserId, caller)) { + s_logger.error("Unable to expunge vm: " + vm.getId()); + accountCleanupNeeded = true; + } + } + + // Mark the account's volumes as destroyed + List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(accountId); + for (VolumeVO volume : volumes) { + if (!volume.getState().equals(Volume.State.Destroy)) { + try { + volumeService.deleteVolume(volume.getId(), caller); + } catch (Exception ex) { + s_logger.warn("Failed to cleanup volumes as a part of account id=" + accountId + " cleanup due to Exception: ", ex); + accountCleanupNeeded = true; + } + } + } + + // delete remote access vpns and associated users + List<RemoteAccessVpnVO> remoteAccessVpns = _remoteAccessVpnDao.findByAccount(accountId); + List<VpnUserVO> vpnUsers = _vpnUser.listByAccount(accountId); + + for (VpnUserVO vpnUser : vpnUsers) { + _remoteAccessVpnMgr.removeVpnUser(accountId, vpnUser.getUsername(), caller); + } + + try { + for (RemoteAccessVpnVO vpn : remoteAccessVpns) { + _remoteAccessVpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, false); + } + } catch (ResourceUnavailableException ex) { + s_logger.warn("Failed to cleanup remote access vpn resources as a part of account id=" + accountId + " cleanup due to Exception: ", ex); + accountCleanupNeeded = true; + } + + // Cleanup security groups + int numRemoved = _securityGroupDao.removeByAccountId(accountId); + s_logger.info("deleteAccount: Deleted " + numRemoved + " network groups for account " + accountId); + + // Cleanup affinity groups + int numAGRemoved = _affinityGroupDao.removeByAccountId(accountId); + s_logger.info("deleteAccount: Deleted " + numAGRemoved + " affinity groups for account " + accountId); + + // Delete all the networks + boolean networksDeleted = true; + s_logger.debug("Deleting networks for account " + account.getId()); + List<NetworkVO> networks = _networkDao.listByOwner(accountId); + if (networks != null) { + for (NetworkVO network : networks) { + + ReservationContext context = new ReservationContextImpl(null, null, getActiveUser(callerUserId), caller); + + if (!_networkMgr.destroyNetwork(network.getId(), context, false)) { + s_logger.warn("Unable to destroy network " + network + " as a part of account id=" + accountId + " cleanup."); + accountCleanupNeeded = true; + networksDeleted = false; + } else { + s_logger.debug("Network " + network.getId() + " successfully deleted as a part of account id=" + accountId + " cleanup."); + } + } + } + + // Delete all VPCs + boolean vpcsDeleted = true; + s_logger.debug("Deleting vpcs for account " + account.getId()); + List<? extends Vpc> vpcs = _vpcMgr.getVpcsForAccount(account.getId()); + for (Vpc vpc : vpcs) { + + if (!_vpcMgr.destroyVpc(vpc, caller, callerUserId)) { + s_logger.warn("Unable to destroy VPC " + vpc + " as a part of account id=" + accountId + " cleanup."); + accountCleanupNeeded = true; + vpcsDeleted = false; + } else { + s_logger.debug("VPC " + vpc.getId() + " successfully deleted as a part of account id=" + accountId + " cleanup."); + } + } + + if (networksDeleted && vpcsDeleted) { + // release ip addresses belonging to the account + List<? extends IpAddress> ipsToRelease = _ipAddressDao.listByAccount(accountId); + for (IpAddress ip : ipsToRelease) { + s_logger.debug("Releasing ip " + ip + " as a part of account id=" + accountId + " cleanup"); + if (!_ipAddrMgr.disassociatePublicIpAddress(ip.getId(), callerUserId, caller)) { + s_logger.warn("Failed to release ip address " + ip + " as a part of account id=" + accountId + " clenaup"); + accountCleanupNeeded = true; + } + } + } + + // Delete Site 2 Site VPN customer gateway + s_logger.debug("Deleting site-to-site VPN customer gateways for account " + accountId); + if (!_vpnMgr.deleteCustomerGatewayByAccount(accountId)) { + s_logger.warn("Fail to delete site-to-site VPN customer gateways for account " + accountId); + } + + // Delete autoscale resources if any + try { + _autoscaleMgr.cleanUpAutoScaleResources(accountId); + } catch (CloudRuntimeException ex) { + s_logger.warn("Failed to cleanup AutoScale resources as a part of account id=" + accountId + " cleanup due to exception:", ex); + accountCleanupNeeded = true; + } + + // release account specific Virtual vlans (belong to system Public Network) - only when networks are cleaned + // up successfully + if (networksDeleted) { + if (!_configMgr.releaseAccountSpecificVirtualRanges(accountId)) { + accountCleanupNeeded = true; + } else { + s_logger.debug("Account specific Virtual IP ranges " + " are successfully released as a part of account id=" + accountId + " cleanup."); + } + } + + // release account specific guest vlans + List<AccountGuestVlanMapVO> maps = _accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(accountId); + for (AccountGuestVlanMapVO map : maps) { + _dataCenterVnetDao.releaseDedicatedGuestVlans(map.getId()); + } + int vlansReleased = _accountGuestVlanMapDao.removeByAccountId(accountId); + s_logger.info("deleteAccount: Released " + vlansReleased + " dedicated guest vlan ranges from account " + accountId); + + // release account specific acquired portable IP's. Since all the portable IP's must have been already + // disassociated with VPC/guest network (due to deletion), so just mark portable IP as free. + List<? extends IpAddress> ipsToRelease = _ipAddressDao.listByAccount(accountId); + for (IpAddress ip : ipsToRelease) { + if (ip.isPortable()) { + s_logger.debug("Releasing portable ip " + ip + " as a part of account id=" + accountId + " cleanup"); + _ipAddrMgr.releasePortableIpAddress(ip.getId()); + } + } + + // release dedication if any + List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByAccountId(accountId); + if (dedicatedResources != null && !dedicatedResources.isEmpty()) { + s_logger.debug("Releasing dedicated resources for account " + accountId); + for (DedicatedResourceVO dr : dedicatedResources) { + if (!_dedicatedDao.remove(dr.getId())) { + s_logger.warn("Fail to release dedicated resources for account " + accountId); + } + } + } + + // Updating and deleting the resourceLimit and resourceCount should be the last step in cleanupAccount +// process. + // Update resource count for this account and for parent domains. + List<ResourceCountVO> resourceCounts = _resourceCountDao.listByOwnerId(accountId, ResourceOwnerType.Account); + for (ResourceCountVO resourceCount : resourceCounts) { + _resourceLimitMgr.decrementResourceCount(accountId, resourceCount.getType(), resourceCount.getCount()); + } + + // Delete resource count and resource limits entries set for this account (if there are any). + _resourceCountDao.removeEntriesByOwner(accountId, ResourceOwnerType.Account); + _resourceLimitDao.removeEntriesByOwner(accountId, ResourceOwnerType.Account); + + // Delete ssh keypairs + List<SSHKeyPairVO> sshkeypairs = _sshKeyPairDao.listKeyPairs(accountId, account.getDomainId()); + for (SSHKeyPairVO keypair : sshkeypairs) { + _sshKeyPairDao.remove(keypair.getId()); + } + return true; + } catch (Exception ex) { + s_logger.warn("Failed to cleanup account " + account + " due to ", ex); + accountCleanupNeeded = true; + return true; + } finally { + s_logger.info("Cleanup for account " + account.getId() + (accountCleanupNeeded ? " is needed." : " is not needed.")); + if (accountCleanupNeeded) { + _accountDao.markForCleanup(accountId); + } else { + account.setNeedsCleanup(false); + _accountDao.update(accountId, account); + } + } + } + + @Override + public boolean disableAccount(long accountId) throws ConcurrentOperationException, ResourceUnavailableException { + boolean success = false; + if (accountId <= 2) { + if (s_logger.isInfoEnabled()) { + s_logger.info("disableAccount -- invalid account id: " + accountId); + } + return false; + } + + AccountVO account = _accountDao.findById(accountId); + if ((account == null) || (account.getState().equals(State.disabled) && !account.getNeedsCleanup())) { + success = true; + } else { + AccountVO acctForUpdate = _accountDao.createForUpdate(); + acctForUpdate.setState(State.disabled); + success = _accountDao.update(Long.valueOf(accountId), acctForUpdate); + + if (success) { + boolean disableAccountResult = false; + try { + disableAccountResult = doDisableAccount(accountId); + } finally { + if (!disableAccountResult) { + s_logger.warn("Failed to disable account " + account + " resources as a part of disableAccount call, marking the account for cleanup"); + _accountDao.markForCleanup(accountId); + } else { + acctForUpdate = _accountDao.createForUpdate(); + account.setNeedsCleanup(false); + _accountDao.update(accountId, account); + } + } + } + } + return success; + } + + private boolean doDisableAccount(long accountId) throws ConcurrentOperationException, ResourceUnavailableException { + List<VMInstanceVO> vms = _vmDao.listByAccountId(accountId); + boolean success = true; + for (VMInstanceVO vm : vms) { + try { + try { + _itMgr.advanceStop(vm.getUuid(), false); + } catch (OperationTimedoutException ote) { + s_logger.warn("Operation for stopping vm timed out, unable to stop vm " + vm.getHostName(), ote); + success = false; + } + } catch (AgentUnavailableException aue) { + s_logger.warn("Agent running on host " + vm.getHostId() + " is unavailable, unable to stop vm " + vm.getHostName(), aue); + success = false; + } + } + + return success; + } + + @Override + @ActionEvents({@ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_CREATE, eventDescription = "creating Account"), + @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")}) + public UserAccount createUserAccount(final String userName, final String password, final String firstName, final String lastName, final String email, final String timezone, String accountName, + final short accountType, final Long roleId, Long domainId, final String networkDomain, final Map<String, String> details, String accountUUID, final String userUUID) { + + return createUserAccount(userName, password, firstName, lastName, email, timezone, accountName, accountType, roleId, domainId, networkDomain, details, accountUUID, userUUID, + User.Source.UNKNOWN); + } + + // /////////////////////////////////////////////////// + // ////////////// API commands ///////////////////// + // /////////////////////////////////////////////////// + + @Override + @DB + @ActionEvents({@ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_CREATE, eventDescription = "creating Account"), + @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")}) + public UserAccount createUserAccount(final String userName, final String password, final String firstName, final String lastName, final String email, final String timezone, String accountName, + final short accountType, final Long roleId, Long domainId, final String networkDomain, final Map<String, String> details, String accountUUID, final String userUUID, + final User.Source source) { + + if (accountName == null) { + accountName = userName; + } + if (domainId == null) { + domainId = Domain.ROOT_DOMAIN; + } + + if (StringUtils.isEmpty(userName)) { + throw new InvalidParameterValueException("Username is empty"); + } + + if (StringUtils.isEmpty(firstName)) { + throw new InvalidParameterValueException("Firstname is empty"); + } + + if (StringUtils.isEmpty(lastName)) { + throw new InvalidParameterValueException("Lastname is empty"); + } + + // Validate domain + Domain domain = _domainMgr.getDomain(domainId); + if (domain == null) { + throw new InvalidParameterValueException("The domain " + domainId + " does not exist; unable to create account"); + } + + // Check permissions + checkAccess(getCurrentCallingAccount(), domain); + + if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { + throw new InvalidParameterValueException("The user " + userName + " already exists in domain " + domainId); + } + + if (networkDomain != null && networkDomain.length() > 0) { + if (!NetUtils.verifyDomainName(networkDomain)) { + throw new InvalidParameterValueException( + "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'); can't start or end with \"-\""); + } + } + + final String accountNameFinal = accountName; + final Long domainIdFinal = domainId; + final String accountUUIDFinal = accountUUID; + Pair<Long, Account> pair = Transaction.execute(new TransactionCallback<Pair<Long, Account>>() { + @Override + public Pair<Long, Account> doInTransaction(TransactionStatus status) { + // create account + String accountUUID = accountUUIDFinal; + if (accountUUID == null) { + accountUUID = UUID.randomUUID().toString(); + } + AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, accountUUID); + long accountId = account.getId(); + + // create the first user for the account + UserVO user = createUser(accountId, userName, password, firstName, lastName, email, timezone, userUUID, source); + + if (accountType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) { + // set registration token + byte[] bytes = (domainIdFinal + accountNameFinal + userName + System.currentTimeMillis()).getBytes(); + String registrationToken = UUID.nameUUIDFromBytes(bytes).toString(); + user.setRegistrationToken(registrationToken); + } + + return new Pair<Long, Account>(user.getId(), account); + } + }); + + long userId = pair.first(); + Account account = pair.second(); + + // create correct account and group association based on accountType + if (accountType != Account.ACCOUNT_TYPE_PROJECT) { + Map<Long, Long> accountGroupMap = new HashMap<Long, Long>(); + accountGroupMap.put(account.getId(), new Long(accountType + 1)); + _messageBus.publish(_name, MESSAGE_ADD_ACCOUNT_EVENT, PublishScope.LOCAL, accountGroupMap); + } + + CallContext.current().putContextParameter(Account.class, account.getUuid()); + + // check success + return _userAccountDao.findById(userId); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User") + public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID, + User.Source source) { + // default domain to ROOT if not specified + if (domainId == null) { + domainId = Domain.ROOT_DOMAIN; + } + + Domain domain = _domainMgr.getDomain(domainId); + if (domain == null) { + throw new CloudRuntimeException("The domain " + domainId + " does not exist; unable to create user"); + } else if (domain.getState().equals(Domain.State.Inactive)) { + throw new CloudRuntimeException("The user cannot be created as domain " + domain.getName() + " is being deleted"); + } + + checkAccess(getCurrentCallingAccount(), domain); + + Account account = _accountDao.findEnabledAccount(accountName, domainId); + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain id=" + domainId + " to create user"); + } + + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("Account id : " + account.getId() + " is a system account, can't add a user to it"); + } + + if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { + throw new CloudRuntimeException("The user " + userName + " already exists in domain " + domainId); + } + UserVO user = null; + user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source); + return user; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User") + public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID) { + + return createUser(userName, password, firstName, lastName, email, timeZone, accountName, domainId, userUUID, User.Source.UNKNOWN); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_UPDATE, eventDescription = "Updating User") + public UserAccount updateUser(UpdateUserCmd updateUserCmd) { + UserVO user = retrieveAndValidateUser(updateUserCmd); + s_logger.debug("Updating user with Id: " + user.getUuid()); + + validateAndUpdatApiAndSecretKeyIfNeeded(updateUserCmd, user); + Account account = retrieveAndValidateAccount(user); + + validateAndUpdateFirstNameIfNeeded(updateUserCmd, user); + validateAndUpdateLastNameIfNeeded(updateUserCmd, user); + validateAndUpdateUsernameIfNeeded(updateUserCmd, user, account); + + validateUserPasswordAndUpdateIfNeeded(updateUserCmd.getPassword(), user, updateUserCmd.getCurrentPassword()); + String email = updateUserCmd.getEmail(); + if (StringUtils.isNotBlank(email)) { + user.setEmail(email); + } + String timezone = updateUserCmd.getTimezone(); + if (StringUtils.isNotBlank(timezone)) { + user.setTimezone(timezone); + } + _userDao.update(user.getId(), user); + return _userAccountDao.findById(user.getId()); + } + + /** + * Updates the password in the user POJO if needed. If no password is provided, then the password is not updated. + * The following validations are executed if 'password' is not null. Admins (root admins or domain admins) can execute password updates without entering the current password. + * <ul> + * <li> If 'password' is blank, we throw an {@link InvalidParameterValueException}; + * <li> If 'current password' is not provided and user is not an Admin, we throw an {@link InvalidParameterValueException}; + * <li> If a normal user is calling this method, we use {@link #validateCurrentPassword(UserVO, String)} to check if the provided old password matches the database one; + * </ul> + * + * If all checks pass, we encode the given password with the most preferable password mechanism given in {@link #_userPasswordEncoders}. + */ + protected void validateUserPasswordAndUpdateIfNeeded(String newPassword, UserVO user, String currentPassword) { + if (newPassword == null) { + s_logger.trace("No new password to update for user: " + user.getUuid()); + return; + } + if (StringUtils.isBlank(newPassword)) { + throw new InvalidParameterValueException("Password cannot be empty or blank."); + } + Account callingAccount = getCurrentCallingAccount(); + boolean isRootAdminExecutingPasswordUpdate = callingAccount.getId() == Account.ACCOUNT_ID_SYSTEM || isRootAdmin(callingAccount.getId()); + boolean isDomainAdmin = isDomainAdmin(callingAccount.getId()); + boolean isAdmin = isDomainAdmin || isRootAdminExecutingPasswordUpdate; + if (isAdmin) { + s_logger.trace(String.format("Admin account [uuid=%s] executing password update for user [%s] ", callingAccount.getUuid(), user.getUuid())); + } + if (!isAdmin && StringUtils.isBlank(currentPassword)) { + throw new InvalidParameterValueException("To set a new password the current password must be provided."); + } + if (CollectionUtils.isEmpty(_userPasswordEncoders)) { + throw new CloudRuntimeException("No user authenticators configured!"); + } + if (!isAdmin) { + validateCurrentPassword(user, currentPassword); + } + UserAuthenticator userAuthenticator = _userPasswordEncoders.get(0); + String newPasswordEncoded = userAuthenticator.encode(newPassword); + user.setPassword(newPasswordEncoded); + } + + /** + * Iterates over all configured user authenticators and tries to authenticate the user using the current password. + * If the user is authenticated with success, we have nothing else to do here; otherwise, an {@link InvalidParameterValueException} is thrown. + */ + protected void validateCurrentPassword(UserVO user, String currentPassword) { + AccountVO userAccount = _accountDao.findById(user.getAccountId()); + boolean currentPasswordMatchesDataBasePassword = false; + for (UserAuthenticator userAuthenticator : _userPasswordEncoders) { + Pair<Boolean, ActionOnFailedAuthentication> authenticationResult = userAuthenticator.authenticate(user.getUsername(), currentPassword, userAccount.getDomainId(), null); + if (authenticationResult == null) { + s_logger.trace(String.format("Authenticator [%s] is returning null for the authenticate mehtod.", userAuthenticator.getClass())); + continue; + } + if (BooleanUtils.toBoolean(authenticationResult.first())) { + s_logger.debug(String.format("User [id=%s] re-authenticated [authenticator=%s] during password update.", user.getUuid(), userAuthenticator.getName())); + currentPasswordMatchesDataBasePassword = true; + break; + } + } + if (!currentPasswordMatchesDataBasePassword) { + throw new InvalidParameterValueException("Current password is incorrect."); + } + } + + /** + * Validates the user 'username' if provided. The 'username' cannot be blank (when provided). + * <ul> + * <li> If the 'username' is not provided, we do not update it (setting to null) in the User POJO. + * <li> If the 'username' is blank, we throw an {@link InvalidParameterValueException}. + * <li> The username must be unique in each domain. Therefore, if there is already another user with the same username, an {@link InvalidParameterValueException} is thrown. + * </ul> + */ + protected void validateAndUpdateUsernameIfNeeded(UpdateUserCmd updateUserCmd, UserVO user, Account account) { + String userName = updateUserCmd.getUsername(); + if (userName == null) { + return; + } + if (StringUtils.isBlank(userName)) { + throw new InvalidParameterValueException("Username cannot be empty."); + } + List<UserVO> duplicatedUsers = _userDao.findUsersByName(userName); + for (UserVO duplicatedUser : duplicatedUsers) { + if (duplicatedUser.getId() == user.getId()) { + continue; + } + Account duplicatedUserAccountWithUserThatHasTheSameUserName = _accountDao.findById(duplicatedUser.getAccountId()); + if (duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId() == account.getDomainId()) { + DomainVO domain = _domainDao.findById(duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId()); + throw new InvalidParameterValueException(String.format("Username [%s] already exists in domain [id=%s,name=%s]", duplicatedUser.getUsername(), domain.getUuid(), domain.getName())); + } + } + user.setUsername(userName); + } + + /** + * Validates the user 'lastName' if provided. The 'lastName' cannot be blank (when provided). + * <ul> + * <li> If the 'lastName' is not provided, we do not update it (setting to null) in the User POJO. + * <li> If the 'lastName' is blank, we throw an {@link InvalidParameterValueException}. + * </ul> + */ + protected void validateAndUpdateLastNameIfNeeded(UpdateUserCmd updateUserCmd, UserVO user) { + String lastName = updateUserCmd.getLastname(); + if (lastName != null) { + if (StringUtils.isBlank(lastName)) { + throw new InvalidParameterValueException("Lastname cannot be empty."); + } + + user.setLastname(lastName); + } + } + + /** + * Validates the user 'firstName' if provided. The 'firstName' cannot be blank (when provided). + * <ul> + * <li> If the 'firstName' is not provided, we do not update it (setting to null) in the User POJO. + * <li> If the 'firstName' is blank, we throw an {@link InvalidParameterValueException}. + * </ul> + */ + protected void validateAndUpdateFirstNameIfNeeded(UpdateUserCmd updateUserCmd, UserVO user) { + String firstName = updateUserCmd.getFirstname(); + if (firstName != null) { + if (StringUtils.isBlank(firstName)) { + throw new InvalidParameterValueException("Firstname cannot be empty."); + } + user.setFirstname(firstName); + } + } + + /** + * Searches an account for the given users. Then, we validate it as follows: + * <ul> + * <li>If no account is found for the given user, we throw a {@link CloudRuntimeException}. There must be something wrong in the database for this case. + * <li>If the account is of {@link Account#ACCOUNT_TYPE_PROJECT}, we throw an {@link InvalidParameterValueException}. + * <li>If the account is of {@link Account#ACCOUNT_ID_SYSTEM}, we throw an {@link InvalidParameterValueException}. + * </ul> + * + * Afterwards, we check if the logged user has access to the user being updated via {@link #checkAccess(Account, AccessType, boolean, ControlledEntity...)} + */ + protected Account retrieveAndValidateAccount(UserVO user) { + Account account = _accountDao.findById(user.getAccountId()); + if (account == null) { + throw new CloudRuntimeException("Unable to find user account with ID: " + user.getAccountId()); + } + if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find user with ID: " + user.getUuid()); + } + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("user UUID : " + user.getUuid() + " is a system account; update is not allowed."); + } + checkAccess(getCurrentCallingAccount(), AccessType.OperateEntry, true, account); + return account; + } + + /** + * Returns the calling account using the method {@link CallContext#getCallingAccount()}. + * We are introducing this method to avoid using 'PowerMockRunner' in unit tests. Then, we can mock the calls to this method, which facilitates the development of test cases. + */ + protected Account getCurrentCallingAccount() { + return CallContext.current().getCallingAccount(); + } + + /** + * Validates user API and Secret keys. If a new pair of keys is provided, we update them in the user POJO. + * <ul> + * <li>When updating the keys, it must be provided a pair (API and Secret keys); otherwise, an {@link InvalidParameterValueException} is thrown. + * <li>If a pair of keys is provided, we validate to see if there is an user already using the provided API key. If there is someone else using, we throw an {@link InvalidParameterValueException} because two users cannot have the same API key. + * </ul> + */ + protected void validateAndUpdatApiAndSecretKeyIfNeeded(UpdateUserCmd updateUserCmd, UserVO user) { + String apiKey = updateUserCmd.getApiKey(); + String secretKey = updateUserCmd.getSecretKey(); + + boolean isApiKeyBlank = StringUtils.isBlank(apiKey); + boolean isSecretKeyBlank = StringUtils.isBlank(secretKey); + if (isApiKeyBlank ^ isSecretKeyBlank) { + throw new InvalidParameterValueException("Please provide a userApiKey/userSecretKey pair"); + } + if (isApiKeyBlank && isSecretKeyBlank) { + return; + } + Pair<User, Account> apiKeyOwner = _accountDao.findUserAccountByApiKey(apiKey); + if (apiKeyOwner != null) { + User userThatHasTheProvidedApiKey = apiKeyOwner.first(); + if (userThatHasTheProvidedApiKey.getId() != user.getId()) { + throw new InvalidParameterValueException(String.format("The API key [%s] already exists in the system. Please provide a unique key.", apiKey)); + } + } + user.setApiKey(apiKey); + user.setSecretKey(secretKey); + } + + /** + * Searches for a user with the given userId. If no user is found we throw an {@link InvalidParameterValueException}. + */ + protected UserVO retrieveAndValidateUser(UpdateUserCmd updateUserCmd) { + Long userId = updateUserCmd.getId(); + + UserVO user = _userDao.getUser(userId); + if (user == null) { + throw new InvalidParameterValueException("Unable to find user with id: " + userId); + } + return user; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_DISABLE, eventDescription = "disabling User", async = true) + public UserAccount disableUser(long userId) { + Account caller = getCurrentCallingAccount(); + + // Check if user exists in the system + User user = _userDao.findById(userId); + if (user == null || user.getRemoved() != null) { + throw new InvalidParameterValueException("Unable to find active user by id " + userId); + } + + Account account = _accountDao.findById(user.getAccountId()); + if (account == null) { + throw new InvalidParameterValueException("unable to find user account " + user.getAccountId()); + } + + // don't allow disabling user belonging to project's account + if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find active user by id " + userId); + } + + // If the user is a System user, return an error + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new InvalidParameterValueException("User id : " + userId + " is a system user, disabling is not allowed"); + } + + checkAccess(caller, AccessType.OperateEntry, true, account); + + boolean success = doSetUserStatus(userId, State.disabled); + if (success) { + + CallContext.current().putContextParameter(User.class, user.getUuid()); + + // user successfully disabled + return _userAccountDao.findById(userId); + } else { + throw new CloudRuntimeException("Unable to disable user " + userId); + } + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_USER_ENABLE, eventDescription = "enabling User") + public UserAccount enableUser(final long userId) { + + Account caller = getCurrentCallingAccount(); + + // Check if user exists in the system + final User user = _userDao.findById(userId); + if (user == null || user.getRemoved() != null) { + throw new InvalidParameterValueException("Unable to find active user by id " + userId); + } + + Account account = _accountDao.findById(user.getAccountId()); + if (account == null) { + throw new InvalidParameterValueException("unable to find user account " + user.getAccountId()); + } + + if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find active user by id " + userId); + } + + // If the user is a System user, return an error + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new InvalidParameterValueException("User id : " + userId + " is a system user, enabling is not allowed"); + } + + checkAccess(caller, AccessType.OperateEntry, true, account); + + boolean success = Transaction.execute(new TransactionCallback<Boolean>() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + boolean success = doSetUserStatus(userId, State.enabled); + + // make sure the account is enabled too + success = success && enableAccount(user.getAccountId()); + + return success; + } + }); + + if (success) { + // whenever the user is successfully enabled, reset the login attempts to zero + updateLoginAttempts(userId, 0, false); + + CallContext.current().putContextParameter(User.class, user.getUuid()); + + return _userAccountDao.findById(userId); + } else { + throw new CloudRuntimeException("Unable to enable user " + userId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_LOCK, eventDescription = "locking User") + public UserAccount lockUser(long userId) { + Account caller = getCurrentCallingAccount(); + + // Check if user with id exists in the system + User user = _userDao.findById(userId); + if (user == null || user.getRemoved() != null) { + throw new InvalidParameterValueException("Unable to find user by id"); + } + + Account account = _accountDao.findById(user.getAccountId()); + if (account == null) { + throw new InvalidParameterValueException("unable to find user account " + user.getAccountId()); + } + + // don't allow to lock user of the account of type Project + if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find user by id"); + } + + // If the user is a System user, return an error. We do not allow this + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("user id : " + userId + " is a system user, locking is not allowed"); + } + + checkAccess(caller, AccessType.OperateEntry, true, account); + + // make sure the account is enabled too + // if the user is either locked already or disabled already, don't change state...only lock currently enabled +// users + boolean success = true; + if (user.getState().equals(State.locked)) { + // already locked...no-op + return _userAccountDao.findById(userId); + } else if (user.getState().equals(State.enabled)) { + success = doSetUserStatus(user.getId(), State.locked); + + boolean lockAccount = true; + List<UserVO> allUsersByAccount = _userDao.listByAccount(user.getAccountId()); + for (UserVO oneUser : allUsersByAccount) { + if (oneUser.getState().equals(State.enabled)) { + lockAccount = false; + break; + } + } + + if (lockAccount) { + success = (success && lockAccount(user.getAccountId())); + } + } else { + if (s_logger.isInfoEnabled()) { + s_logger.info("Attempting to lock a non-enabled user, current state is " + user.getState() + " (userId: " + user.getId() + "), locking failed."); + } + success = false; + } + + if (success) { + + CallContext.current().putContextParameter(User.class, user.getUuid()); + + return _userAccountDao.findById(userId); + } else { + throw new CloudRuntimeException("Unable to lock user " + userId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_DELETE, eventDescription = "deleting account", async = true) + public boolean deleteUserAccount(long accountId) { + + CallContext ctx = CallContext.current(); + long callerUserId = ctx.getCallingUserId(); + Account caller = ctx.getCallingAccount(); + + // If the user is a System user, return an error. We do not allow this + AccountVO account = _accountDao.findById(accountId); + + if (account == null || account.getRemoved() != null) { + if (account != null) { + s_logger.info("The account:" + account.getAccountName() + " is already removed"); + } + return true; + } + + // don't allow removing Project account + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("The specified account does not exist in the system"); + } + + checkAccess(caller, null, true, account); + + // don't allow to delete default account (system and admin) + if (account.isDefault()) { + throw new InvalidParameterValueException("The account is default and can't be removed"); + } + + // Account that manages project(s) can't be removed + List<Long> managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); + if (!managedProjectIds.isEmpty()) { + StringBuilder projectIds = new StringBuilder(); + for (Long projectId : managedProjectIds) { + projectIds.append(projectId + ", "); + } + + throw new InvalidParameterValueException("The account id=" + accountId + " manages project(s) with ids " + projectIds + "and can't be removed"); + } + + CallContext.current().putContextParameter(Account.class, account.getUuid()); + + return deleteAccount(account, callerUserId, caller); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_ENABLE, eventDescription = "enabling account", async = true) + public AccountVO enableAccount(String accountName, Long domainId, Long accountId) { + + // Check if account exists + Account account = null; + if (accountId != null) { + account = _accountDao.findById(accountId); + } else { + account = _accountDao.findActiveAccount(accountName, domainId); + } + + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("Account id : " + accountId + " is a system account, enable is not allowed"); + } + + // Check if user performing the action is allowed to modify this account + Account caller = getCurrentCallingAccount(); + checkAccess(caller, AccessType.OperateEntry, true, account); + + boolean success = enableAccount(account.getId()); + if (success) { + + CallContext.current().putContextParameter(Account.class, account.getUuid()); + + return _accountDao.findById(account.getId()); + } else { + throw new CloudRuntimeException("Unable to enable account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_DISABLE, eventDescription = "locking account", async = true) + public AccountVO lockAccount(String accountName, Long domainId, Long accountId) { + Account caller = getCurrentCallingAccount(); + + Account account = null; + if (accountId != null) { + account = _accountDao.findById(accountId); + } else { + account = _accountDao.findActiveAccount(accountName, domainId); + } + + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find active account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("Account id : " + accountId + " is a system account, lock is not allowed"); + } + + checkAccess(caller, AccessType.OperateEntry, true, account); + + if (lockAccount(account.getId())) { + CallContext.current().putContextParameter(Account.class, account.getUuid()); + return _accountDao.findById(account.getId()); + } else { + throw new CloudRuntimeException("Unable to lock account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_DISABLE, eventDescription = "disabling account", async = true) + public AccountVO disableAccount(String accountName, Long domainId, Long accountId) throws ConcurrentOperationException, ResourceUnavailableException { + Account caller = getCurrentCallingAccount(); + + Account account = null; + if (accountId != null) { + account = _accountDao.findById(accountId); + } else { + account = _accountDao.findActiveAccount(accountName, domainId); + } + + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + throw new InvalidParameterValueException("Unable to find account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new PermissionDeniedException("Account id : " + accountId + " is a system account, disable is not allowed"); + } + + checkAccess(caller, AccessType.OperateEntry, true, account); + + if (disableAccount(account.getId())) { + CallContext.current().putContextParameter(Account.class, account.getUuid()); + return _accountDao.findById(account.getId()); + } else { + throw new CloudRuntimeException("Unable to update account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_UPDATE, eventDescription = "updating account", async = true) + public AccountVO updateAccount(UpdateAccountCmd cmd) { + Long accountId = cmd.getId(); + Long domainId = cmd.getDomainId(); + String accountName = cmd.getAccountName(); + String newAccountName = cmd.getNewName(); + String networkDomain = cmd.getNetworkDomain(); + final Map<String, String> details = cmd.getDetails(); + + boolean success = false; + Account account = null; + if (accountId != null) { + account = _accountDao.findById(accountId); + } else { + account = _accountDao.findEnabledAccount(accountName, domainId); + } + + // Check if account exists + if (account == null || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { + s_logger.error("Unable to find account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + throw new InvalidParameterValueException("Unable to find account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + + // Don't allow to modify system account + if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { + throw new InvalidParameterValueException("Can not modify system account"); + } + + // Check if user performing the action is allowed to modify this account + checkAccess(getCurrentCallingAccount(), _domainMgr.getDomain(account.getDomainId())); + + // check if the given account name is unique in this domain for updating + Account duplicateAcccount = _accountDao.findActiveAccount(newAccountName, domainId); + if (duplicateAcccount != null && duplicateAcccount.getId() != account.getId()) { + throw new InvalidParameterValueException( + "There already exists an account with the name:" + newAccountName + " in the domain:" + domainId + " with existing account id:" + duplicateAcccount.getId()); + } + + if (networkDomain != null && !networkDomain.isEmpty()) { + if (!NetUtils.verifyDomainName(networkDomain)) { + throw new InvalidParameterValueException( + "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'); can't start or end with \"-\""); + } + } + + final AccountVO acctForUpdate = _accountDao.findById(account.getId()); + acctForUpdate.setAccountName(newAccountName); + + if (networkDomain != null) { + if (networkDomain.isEmpty()) { + acctForUpdate.setNetworkDomain(null); + } else { + acctForUpdate.setNetworkDomain(networkDomain); + } + } + + final Account accountFinal = account; + success = Transaction.execute(new TransactionCallback<Boolean>() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + boolean success = _accountDao.update(accountFinal.getId(), acctForUpdate); + + if (details != null && success) { + _accountDetailsDao.update(accountFinal.getId(), details); + } + + return success; + } + }); + + if (success) { + CallContext.current().putContextParameter(Account.class, account.getUuid()); + return _accountDao.findById(account.getId()); + } else { + throw new CloudRuntimeException("Unable to update account by accountId: " + accountId + " OR by name: " + accountName + " in domain " + domainId); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_DELETE, eventDescription = "deleting User") + public boolean deleteUser(DeleteUserCmd deleteUserCmd) { + 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()); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account") + public boolean moveUser(MoveUserCmd cmd) { + final Long id = cmd.getId(); + UserVO user = getValidUserVO(id); + Account oldAccount = _accountDao.findById(user.getAccountId()); + checkAccountAndAccess(user, oldAccount); + long domainId = oldAccount.getDomainId(); + + long newAccountId = getNewAccountId(domainId, cmd.getAccountName(), cmd.getAccountId()); + + return moveUser(user, newAccountId); + } + + @Override + 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(UserVO user, 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<Boolean>() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + UserVO newUser = new UserVO(user); + user.setExternalEntity(user.getUuid()); + user.setUuid(UUID.randomUUID().toString()); ++ user.setApiKey(null); ++ user.setSecretKey(null); + _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(getCurrentCallingAccount(), 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 (re)moved"); + } + + return user; + } + + protected class AccountCleanupTask extends ManagedContextRunnable { + @Override + protected void runInContext() { + try { + GlobalLock lock = GlobalLock.getInternLock("AccountCleanup"); + if (lock == null) { + s_logger.debug("Couldn't get the global lock"); + return; + } + + if (!lock.lock(30)) { + s_logger.debug("Couldn't lock the db"); + return; + } + + try { + // Cleanup removed accounts + List<AccountVO> removedAccounts = _accountDao.findCleanupsForRemovedAccounts(null); + s_logger.info("Found " + removedAccounts.size() + " removed accounts to cleanup"); + for (AccountVO account : removedAccounts) { + s_logger.debug("Cleaning up " + account.getId()); + cleanupAccount(account, getSystemUser().getId(), getSystemAccount()); + } + + // cleanup disabled accounts + List<AccountVO> disabledAccounts = _accountDao.findCleanupsForDisabledAccounts(); + s_logger.info("Found " + disabledAccounts.size() + " disabled accounts to cleanup"); + for (AccountVO account : disabledAccounts) { + s_logger.debug("Disabling account " + account.getId()); + try { + disableAccount(account.getId()); + } catch (Exception e) { + s_logger.error("Skipping due to error on account " + account.getId(), e); + } + } + + // cleanup inactive domains + List<? extends Domain> inactiveDomains = _domainMgr.findInactiveDomains(); + s_logger.info("Found " + inactiveDomains.size() + " inactive domains to cleanup"); + for (Domain inactiveDomain : inactiveDomains) { + long domainId = inactiveDomain.getId(); + try { + List<AccountVO> accountsForCleanupInDomain = _accountDao.findCleanupsForRemovedAccounts(domainId); + if (accountsForCleanupInDomain.isEmpty()) { + // release dedication if any, before deleting the domain + List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByDomainId(domainId); + if (dedicatedResources != null && !dedicatedResources.isEmpty()) { + s_logger.debug("Releasing dedicated resources for domain" + domainId); + for (DedicatedResourceVO dr : dedicatedResources) { + if (!_dedicatedDao.remove(dr.getId())) { + s_logger.warn("Fail to release dedicated resources for domain " + domainId); + } + } + } + s_logger.debug("Removing inactive domain id=" + domainId); + _domainMgr.removeDomain(domainId); + } else { + s_logger.debug("Can't remove inactive domain id=" + domainId + " as it has accounts that need cleanup"); + } + } catch (Exception e) { + s_logger.error("Skipping due to error on domain " + domainId, e); + } + } + + // cleanup inactive projects + List<ProjectVO> inactiveProjects = _projectDao.listByState(Project.State.Disabled); + s_logger.info("Found " + inactiveProjects.size() + " disabled projects to cleanup"); + for (ProjectVO project : inactiveProjects) { + try { + Account projectAccount = getAccount(project.getProjectAccountId()); + if (projectAccount == null) { + s_logger.debug("Removing inactive project id=" + project.getId()); + _projectMgr.deleteProject(CallContext.current().getCallingAccount(), CallContext.current().getCallingUserId(), project); + } else { + s_logger.debug("Can't remove disabled project " + project + " as it has non removed account id=" + project.getId()); + } + } catch (Exception e) { + s_logger.error("Skipping due to error on project " + project, e); + } + } + + } catch (Exception e) { + s_logger.error("Exception ", e); + } finally { + lock.unlock(); + } + } catch (Exception e) { + s_logger.error("Exception ", e); + } + } + } + + @Override + public Account finalizeOwner(Account caller, String accountName, Long domainId, Long projectId) { + // don't default the owner to the system account + if (caller.getId() == Account.ACCOUNT_ID_SYSTEM && ((accountName == null || domainId == null) && projectId == null)) { + throw new InvalidParameterValueException("Account and domainId are needed for resource creation"); + } + + // projectId and account/domainId can't be specified together + if ((accountName != null && domainId != null) && projectId != null) { + throw new InvalidParameterValueException("ProjectId and account/domainId can't be specified together"); + } + + if (projectId != null) { + Project project = _projectMgr.getProject(projectId); + if (project == null) { + throw new InvalidParameterValueException("Unable to find project by id=" + projectId); + } + + if (!_projectMgr.canAccessProjectAccount(caller, project.getProjectAccountId())) { + throw new PermissionDeniedException("Account " + caller + " is unauthorised to use project id=" + projectId); + } + + return getAccount(project.getProjectAccountId()); + } + + if (isAdmin(caller.getId()) && accountName != null && domainId != null) { + Domain domain = _domainMgr.getDomain(domainId); + if (domain == null) { + throw new InvalidParameterValueException("Unable to find the domain by id=" + domainId); + } + + Account owner = _accountDao.findActiveAccount(accountName, domainId); + if (owner == null) { + throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId); + } + checkAccess(caller, domain); + + return owner; + } else if (!isAdmin(caller.getId()) && accountName != null && domainId != null) { + if (!accountName.equals(caller.getAccountName()) || domainId.longValue() != caller.getDomainId()) { + throw new PermissionDeniedException("Can't create/list resources for account " + accountName + " in domain " + domainId + ", permission denied"); + } else { + return caller; + } + } else { + if ((accountName == null && domainId != null) || (accountName != null && domainId == null)) { + throw new InvalidParameterValueException("AccountName and domainId must be specified together"); + } + // regular user can't create/list resources for other people + return caller; + } + } + + @Override + public Account getActiveAccountByName(String accountName, Long domainId) { + if (accountName == null || domainId == null) { + throw new InvalidParameterValueException("Both accountName and domainId are required for finding active account in the system"); + } else { + return _accountDao.findActiveAccount(accountName, domainId); + } + } + + @Override + public UserAccount getActiveUserAccount(String username, Long domainId) { + return _userAccountDao.getUserAccount(username, domainId); + } + + @Override + public Account getActiveAccountById(long accountId) { + return _accountDao.findById(accountId); + } + + @Override + public Account getAccount(long accountId) { + return _accountDao.findByIdIncludingRemoved(accountId); + } + + @Override + public RoleType getRoleType(Account account) { + if (account == null) { + return RoleType.Unknown; + } + return RoleType.getByAccountType(account.getType()); + } + + @Override + public User getActiveUser(long userId) { + return _userDao.findById(userId); + } + + @Override + public User getUserIncludingRemoved(long userId) { + return _userDao.findByIdIncludingRemoved(userId); + } + + @Override + public User getActiveUserByRegistrationToken(String registrationToken) { + return _userDao.findUserByRegistrationToken(registrationToken); + } + + @Override + public void markUserRegistered(long userId) { + UserVO userForUpdate = _userDao.createForUpdate(); + userForUpdate.setRegistered(true); + _userDao.update(Long.valueOf(userId), userForUpdate); + } + + @Override + @DB + public AccountVO createAccount(final String accountName, final short accountType, final Long roleId, final Long domainId, final String networkDomain, final Map<String, String> details, + final String uuid) { + // Validate domain + Domain domain = _domainMgr.getDomain(domainId); + if (domain == null) { + throw new InvalidParameterValueException("The domain " + domainId + " does not exist; unable to create account"); + } + + if (domain.getState().equals(Domain.State.Inactive)) { + throw new CloudRuntimeException("The account cannot be created as domain " + domain.getName() + " is being deleted"); + } + + if ((domainId != Domain.ROOT_DOMAIN) && (accountType == Account.ACCOUNT_TYPE_ADMIN)) { + throw new InvalidParameterValueException( + "Invalid account type " + accountType + " given for an account in domain " + domainId + "; unable to create user of admin role type in non-ROOT domain."); + } + + // Validate account/user/domain settings + if (_accountDao.findActiveAccount(accountName, domainId) != null) { + throw new InvalidParameterValueException("The specified account: " + accountName + " already exists"); + } + + if (networkDomain != null) { + if (!NetUtils.verifyDomainName(networkDomain)) { + throw new InvalidParameterValueException( + "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'); can't start or end with \"-\""); + } + } + + // Verify account type + if ((accountType < Account.ACCOUNT_TYPE_NORMAL) || (accountType > Account.ACCOUNT_TYPE_PROJECT)) { + throw new InvalidParameterValueException("Invalid account type " + accountType + " given; unable to create user"); + } + + if (accountType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) { + List<DataCenterVO> dc = _dcDao.findZonesByDomainId(domainId); + if (dc.isEmpty()) { + throw new InvalidParameterValueException("The account cannot be created as domain " + domain.getName() + " is not associated with any private Zone"); + } + } + + // Create the account + return Transaction.execute(new TransactionCallback<AccountVO>() { + @Override + public AccountVO doInTransaction(TransactionStatus status) { + AccountVO account = _accountDao.persist(new AccountVO(accountName, domainId, networkDomain, accountType, roleId, uuid)); + + if (account == null) { + throw new CloudRuntimeException("Failed to create account name " + accountName + " in domain id=" + domainId); + } + + Long accountId = account.getId(); + + if (details != null) { + _accountDetailsDao.persist(accountId, details); + } + + // Create resource count records for the account + _resourceCountDao.createResourceCounts(accountId, ResourceLimit.ResourceOwnerType.Account); + + // Create default security group + _networkGroupMgr.createDefaultSecurityGroup(accountId); + + return account; + } + }); + } + + protected UserVO createUser(long accountId, String userName, String password, String firstName, String lastName, String email, String timezone, String userUUID, User.Source source) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone); + } + + String encodedPassword = null; + for (UserAuthenticator authenticator : _userPasswordEncoders) { + encodedPassword = authenticator.encode(password); + if (encodedPassword != null) { + break; + } + } + if (encodedPassword == null) { + throw new CloudRuntimeException("Failed to encode password"); + } + + if (userUUID == null) { + userUUID = UUID.randomUUID().toString(); + } + UserVO user = _userDao.persist(new UserVO(accountId, userName, encodedPassword, firstName, lastName, email, timezone, userUUID, source)); + CallContext.current().putContextParameter(User.class, user.getUuid()); + return user; + } + + @Override + public void logoutUser(long userId) { + UserAccount userAcct = _userAccountDao.findById(userId); + if (userAcct != null) { + ActionEventUtils.onActionEvent(userId, userAcct.getAccountId(), userAcct.getDomainId(), EventTypes.EVENT_USER_LOGOUT, "user has logged out"); + } // else log some kind of error event? This likely means the user doesn't exist, or has been deleted... + } + + @Override + public UserAccount authenticateUser(final String username, final String password, final Long domainId, final InetAddress loginIpAddress, final Map<String, Object[]> requestParameters) { + UserAccount user = null; + if (password != null && !password.isEmpty()) { + user = getUserAccount(username, password, domainId, requestParameters); + } else { + String key = _configDao.getValue("security.singlesignon.key"); + if (key == null) { + // the SSO key is gone, don't authenticate + return null; + } + + String singleSignOnTolerance = _configDao.getValue("security.singlesignon.tolerance.millis"); + if (singleSignOnTolerance == null) { + // the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be + // valid), + // don't authenticate + return null; + } + + long tolerance = Long.parseLong(singleSignOnTolerance); + String signature = null; + long timestamp = 0L; + String unsignedRequest = null; + StringBuffer unsignedRequestBuffer = new StringBuffer(); + + // - build a request string with sorted params, make sure it's all lowercase + // - sign the request, verify the signature is the same + List<String> parameterNames = new ArrayList<String>(); + + for (Object paramNameObj : requestParameters.keySet()) { + parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later + } + + Collections.sort(parameterNames); + + try { + for (String paramName : parameterNames) { + // parameters come as name/value pairs in the form String/String[] + String paramValue = ((String[])requestParameters.get(paramName))[0]; + + if ("signature".equalsIgnoreCase(paramName)) { + signature = paramValue; + } else { + if ("timestamp".equalsIgnoreCase(paramName)) { + String timestampStr = paramValue; + try { + // If the timestamp is in a valid range according to our tolerance, verify the request + // signature, otherwise return null to indicate authentication failure + timestamp = Long.parseLong(timestampStr); + long currentTime = System.currentTimeMillis(); + if (Math.abs(currentTime - timestamp) > tolerance) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Expired timestamp passed in to login, current time = " + currentTime + ", timestamp = " + timestamp); + } + return null; + } + } catch (NumberFormatException nfe) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Invalid timestamp passed in to login: " + timestampStr); + } + return null; + } + } + + if (unsignedRequestBuffer.length() != 0) { + unsignedRequestBuffer.append("&"); + } + unsignedRequestBuffer.append(paramName).append("=").append(URLEncoder.encode(paramValue, "UTF-8")); + } + } + + if ((signature == null) || (timestamp == 0L)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp); + } + return null; + } + + unsignedRequest = unsignedRequestBuffer.toString().toLowerCase().replaceAll("\\+", "%20"); + + Mac mac = Mac.getInstance("HmacSHA1"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + mac.init(keySpec); + mac.update(unsignedRequest.getBytes()); + byte[] encryptedBytes = mac.doFinal(); + String computedSignature = new String(Base64.encodeBase64(encryptedBytes)); + boolean equalSig = ConstantTimeComparator.compareStrings(signature, computedSignature); + if (!equalSig) { + s_logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature); + } else { + user = _userAccountDao.getUserAccount(username, domainId); + } + } catch (Exception ex) { + s_logger.error("Exception authenticating user", ex); + return null; + } + } + + if (user != null) { + // don't allow to authenticate system user + if (user.getId() == User.UID_SYSTEM) { + s_logger.error("Failed to authenticate user: " + username + " in domain " + domainId); + return null; + } + // don't allow baremetal system user + if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername())) { + s_logger.error("Won't authenticate user: " + username + " in domain " + domainId); + return null; + } + + // We authenticated successfully by now, let's check if we are allowed to login from the ip address the reqest comes from + final Account account = getAccount(user.getAccountId()); + final DomainVO domain = (DomainVO) _domainMgr.getDomain(account.getDomainId()); + + // Get the CIDRs from where this account is allowed to make calls + final String accessAllowedCidrs = ApiServiceConfiguration.ApiAllowedSourceCidrList.valueIn(account.getId()).replaceAll("\\s", ""); + final Boolean ApiSourceCidrChecksEnabled = ApiServiceConfiguration.ApiSourceCidrChecksEnabled.value(); + + if (ApiSourceCidrChecksEnabled) { + s_logger.debug("CIDRs from which account '" + account.toString() + "' is allowed to perform API calls: " + accessAllowedCidrs); + + // Block when is not in the list of allowed IPs + if (!NetUtils.isIpInCidrList(loginIpAddress, accessAllowedCidrs.split(","))) { + s_logger.warn("Request by account '" + account.toString() + "' was denied since " + loginIpAddress.toString().replaceAll("/", "") + " does not match " + accessAllowedCidrs); + throw new CloudAuthenticationException("Failed to authenticate user '" + username + "' in domain '" + domain.getPath() + "' from ip " + + loginIpAddress.toString().replaceAll("/", "") + "; please provide valid credentials"); + } + } + + // Here all is fine! + if (s_logger.isDebugEnabled()) { + s_logger.debug("User: " + username + " in domain " + domainId + " has successfully logged in"); + } + + ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, "user has logged in from IP Address " + loginIpAddress); + + return user; + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("User: " + username + " in domain " + domainId + " has failed to log in"); + } + return null; + } + } + + private UserAccount getUserAccount(String username, String password, Long domainId, Map<String, Object[]> requestParameters) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Attempting to log in user: " + username + " in domain " + domainId); + } + UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId); + + boolean authenticated = false; + HashSet<ActionOnFailedAuthentication> actionsOnFailedAuthenticaion = new HashSet<ActionOnFailedAuthentication>(); + User.Source userSource = userAccount != null ? userAccount.getSource() : User.Source.UNKNOWN; + for (UserAuthenticator authenticator : _userAuthenticators) { + if (userSource != User.Source.UNKNOWN) { + if (!authenticator.getName().equalsIgnoreCase(userSource.name())) { + continue; + } + } + Pair<Boolean, ActionOnFailedAuthentication> result = authenticator.authenticate(username, password, domainId, requestParameters); + if (result.first()) { + authenticated = true; + break; + } else if (result.second() != null) { + actionsOnFailedAuthenticaion.add(result.second()); + } + } + + boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion.contains(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); + + if (authenticated) { + + Domain domain = _domainMgr.getDomain(domainId); + String domainName = null; + 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())) { + if (s_logger.isInfoEnabled()) { + s_logger.info("User " + username + " in domain " + domainName + " is disabled/locked (or account is disabled/locked)"); + } + throw new CloudAuthenticationException("User " + username + " (or their account) in domain " + domainName + " is disabled/locked. Please contact the administrator."); + } + // Whenever the user is able to log in successfully, reset the login attempts to zero + if (!isInternalAccount(userAccount.getId())) { + updateLoginAttempts(userAccount.getId(), 0, false); + } + + return userAccount; + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Unable to authenticate user with username " + username + " in domain " + domainId); + } + + if (userAccount == null) { + s_logger.warn("Unable to find an user with username " + username + " in domain " + domainId); + return null; + } + + if (userAccount.getState().equalsIgnoreCase(Account.State.enabled.toString())) { + if (!isInternalAccount(userAccount.getId())) { + // Internal accounts are not disabled + int attemptsMade = userAccount.getLoginAttempts() + 1; + if (updateIncorrectLoginCount) { + if (attemptsMade < _allowedLoginAttempts) { + updateLoginAttempts(userAccount.getId(), attemptsMade, false); + s_logger.warn("Login attempt failed. You have " + (_allowedLoginAttempts - attemptsMade) + " attempt(s) remaining"); + } else { + updateLoginAttempts(userAccount.getId(), _allowedLoginAttempts, true); + s_logger.warn("User " + userAccount.getUsername() + " has been disabled due to multiple failed login attempts." + " Please contact admin."); + } + } + } + } else { + s_logger.info("User " + userAccount.getUsername() + " is disabled/locked"); + } + return null; + } + } + + @Override + public Pair<User, Account> findUserByApiKey(String apiKey) { + return _accountDao.findUserAccountByApiKey(apiKey); + } + + @Override + public Map<String, String> getKeys(GetUserKeysCmd cmd) { + final long userId = cmd.getID(); + + User user = getActiveUser(userId); + if (user == null) { + throw new InvalidParameterValueException("Unable to find user by id"); + } + final ControlledEntity account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user. + checkAccess(CallContext.current().getCallingUser(), account); + + Map<String, String> keys = new HashMap<String, String>(); + keys.put("apikey", user.getApiKey()); + keys.put("secretkey", user.getSecretKey()); + + return keys; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY, eventDescription = "register for the developer API keys") + public String[] createApiKeyAndSecretKey(RegisterCmd cmd) { + Account caller = getCurrentCallingAccount(); + final Long userId = cmd.getId(); + + User user = getUserIncludingRemoved(userId); + if (user == null) { + throw new InvalidParameterValueException("unable to find user by id"); + } + + Account account = _accountDao.findById(user.getAccountId()); + checkAccess(caller, null, true, account); + + // don't allow updating system user + if (user.getId() == User.UID_SYSTEM) { + throw new PermissionDeniedException("user id : " + user.getId() + " is system account, update is not allowed"); + } + // don't allow baremetal system user + if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername())) { + throw new PermissionDeniedException("user id : " + user.getId() + " is system account, update is not allowed"); + } + + // generate both an api key and a secret key, update the user table with the keys, return the keys to the user + final String[] keys = new String[2]; + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + keys[0] = createUserApiKey(userId); + keys[1] = createUserSecretKey(userId); + } + }); + + return keys; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY, eventDescription = "register for the developer API keys") + public String[] createApiKeyAndSecretKey(final long userId) { + User user = getUserIncludingRemoved(userId); + if (user == null) { + throw new InvalidParameterValueException("Unable to find user by id"); + } + final String[] keys = new String[2]; + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + keys[0] = AccountManagerImpl.this.createUserApiKey(userId); + keys[1] = AccountManagerImpl.this.createUserSecretKey(userId); + } + }); + return keys; + } + + private String createUserApiKey(long userId) { + try { + UserVO updatedUser = _userDao.createForUpdate(); + + String encodedKey = null; + Pair<User, Account> userAcct = null; + int retryLimit = 10; + do { + // FIXME: what algorithm should we use for API keys? + KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1"); + SecretKey key = generator.generateKey(); + encodedKey = Base64.encodeBase64URLSafeString(key.getEncoded()); + userAcct = _accountDao.findUserAccountByApiKey(encodedKey); + retryLimit--; + } while ((userAcct != null) && (retryLimit >= 0)); + + if (userAcct != null) { + return null; + } + updatedUser.setApiKey(encodedKey); + _userDao.update(userId, updatedUser); + return encodedKey; + } catch (NoSuchAlgorithmException ex) { + s_logger.error("error generating secret key for user id=" + userId, ex); + } + return null; + } + + private String createUserSecretKey(long userId) { + try { + UserVO updatedUser = _userDao.createForUpdate(); + String encodedKey = null; + int retryLimit = 10; + UserVO userBySecretKey = null; + do { + KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1"); + SecretKey key = generator.generateKey(); + encodedKey = Base64.encodeBase64URLSafeString(key.getEncoded()); + userBySecretKey = _userDao.findUserBySecretKey(encodedKey); + retryLimit--; + } while ((userBySecretKey != null) && (retryLimit >= 0)); + + if (userBySecretKey != null) { + return null; + } + + updatedUser.setSecretKey(encodedKey); + _userDao.update(userId, updatedUser); + return encodedKey; + } catch (NoSuchAlgorithmException ex) { + s_logger.error("error generating secret key for user id=" + userId, ex); + } + return null; + } + + @Override + public void buildACLSearchBuilder(SearchBuilder<? extends ControlledEntity> sb, Long domainId, boolean isRecursive, List<Long> permittedAccounts, + ListProjectResourcesCriteria listProjectResourcesCriteria) { + + if (sb.entity() instanceof IPAddressVO) { + sb.and("accountIdIN", ((IPAddressVO)sb.entity()).getAllocatedToAccountId(), SearchCriteria.Op.IN); + sb.and("domainId", ((IPAddressVO)sb.entity()).getAllocatedInDomainId(), SearchCriteria.Op.EQ); + } else if (sb.entity() instanceof ProjectInvitationVO) { + sb.and("accountIdIN", ((ProjectInvitationVO)sb.entity()).getForAccountId(), SearchCriteria.Op.IN); + sb.and("domainId", ((ProjectInvitationVO)sb.entity()).getInDomainId(), SearchCriteria.Op.EQ); + } else { + sb.and("accountIdIN", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ); + } + + if (((permittedAccounts.isEmpty()) && (domainId != null) && isRecursive)) { + // if accountId isn't specified, we can do a domain match for the admin case if isRecursive is true + SearchBuilder<DomainVO> domainSearch = _domainDao.createSearchBuilder(); + domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE); + + if (sb.entity() instanceof IPAddressVO) { + sb.join("domainSearch", domainSearch, ((IPAddressVO)sb.entity()).getAllocatedInDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } else if (sb.entity() instanceof ProjectInvitationVO) { + sb.join("domainSearch", domainSearch, ((ProjectInvitationVO)sb.entity()).getInDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } else { + sb.join("domainSearch", domainSearch, sb.entity().getDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } + + } + if (listProjectResourcesCriteria != null) { + SearchBuilder<AccountVO> accountSearch = _accountDao.createSearchBuilder(); + if (listProjectResourcesCriteria == Project.ListProjectResourcesCriteria.ListProjectResourcesOnly) { + accountSearch.and("type", accountSearch.entity().getType(), SearchCriteria.Op.EQ); + } else if (listProjectResourcesCriteria == Project.ListProjectResourcesCriteria.SkipProjectResources) { + accountSearch.and("type", accountSearch.entity().getType(), SearchCriteria.Op.NEQ); + } + + if (sb.entity() instanceof IPAddressVO) { + sb.join("accountSearch", accountSearch, ((IPAddressVO)sb.entity()).getAllocatedToAccountId(), accountSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } else if (sb.entity() instanceof ProjectInvitationVO) { + sb.join("accountSearch", accountSearch, ((ProjectInvitationVO)sb.entity()).getForAccountId(), accountSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } else { + sb.join("accountSearch", accountSearch, sb.entity().getAccountId(), accountSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } + } + } + + @Override + public void buildACLSearchCriteria(SearchCriteria<? extends ControlledEntity> sc, Long domainId, boolean isRecursive, List<Long> permittedAccounts, + ListProjectResourcesCriteria listProjectResourcesCriteria) { + + if (listProjectResourcesCriteria != null) { + sc.setJoinParameters("accountSearch", "type", Account.ACCOUNT_TYPE_PROJECT); + } + + if (!permittedAccounts.isEmpty()) { + sc.setParameters("accountIdIN", permittedAccounts.toArray()); + } else if (domainId != null) { + DomainVO domain = _domainDao.findById(domainId); + if (isRecursive) { + sc.setJoinParameters("domainSearch", "path", domain.getPath() + "%"); + } else { + sc.setParameters("domainId", domainId); + } + } + } + + //TODO: deprecate this to use the new buildACLSearchParameters with permittedDomains, permittedAccounts, and permittedResources as return + @Override + public void buildACLSearchParameters(Account caller, Long id, String accountName, Long projectId, List<Long> permittedAccounts, + Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject, boolean listAll, boolean forProjectInvitation) { + Long domainId = domainIdRecursiveListProject.first(); + if (domainId != null) { + Domain domain = _domainDao.findById(domainId); + if (domain == null) { + throw new InvalidParameterValueException("Unable to find domain by id " + domainId); + } + // check permissions + checkAccess(caller, domain); + } + + if (accountName != null) { + if (projectId != null) { + throw new InvalidParameterValueException("Account and projectId can't be specified together"); + } + + Account userAccount = null; + Domain domain = null; + if (domainId != null) { + userAccount = _accountDao.findActiveAccount(accountName, domainId); + domain = _domainDao.findById(domainId); + } else { + userAccount = _accountDao.findActiveAccount(accountName, caller.getDomainId()); + domain = _domainDao.findById(caller.getDomainId()); + } + + if (userAccount != null) { + checkAccess(caller, null, false, userAccount); + // check permissions + permittedAccounts.add(userAccount.getId()); + } else { + throw new InvalidParameterValueException("could not find account " + accountName + " in domain " + domain.getUuid()); + } + } + + // set project information + if (projectId != null) { + if (!forProjectInvitation) { + if (projectId.longValue() == -1) { + if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) { + permittedAccounts.addAll(_projectMgr.listPermittedProjectAccounts(caller.getId())); + + //permittedAccounts can be empty when the caller is not a part of any project (a domain account) + if (permittedAccounts.isEmpty()) { + permittedAccounts.add(caller.getId()); + } + } else { + domainIdRecursiveListProject.third(Project.ListProjectResourcesCriteria.ListProjectResourcesOnly); + } + } else { + Project project = _projectMgr.getProject(projectId); + if (project == null) { + throw new InvalidParameterValueException("Unable to find project by id " + projectId); + } + if (!_projectMgr.canAccessProjectAccount(caller, project.getProjectAccountId())) { + throw new PermissionDeniedException("Account " + caller + " can't access project id=" + projectId); + } + permittedAccounts.add(project.getProjectAccountId()); + } + } + } else { + if (id == null) { + domainIdRecursiveListProject.third(Project.ListProjectResourcesCriteria.SkipProjectResources); + } + if (permittedAccounts.isEmpty() && domainId == null) { + if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) { + permittedAccounts.add(caller.getId()); + } else if (!listAll) { + if (id == null) { + permittedAccounts.add(caller.getId()); + } else if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { + domainIdRecursiveListProject.first(caller.getDomainId()); + domainIdRecursiveListProject.second(true); + } + } else if (domainId == null) { + if (caller.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) { + domainIdRecursiveListProject.first(caller.getDomainId()); + domainIdRecursiveListProject.second(true); + } + } + } else if (domainId != null) { + if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) { + permittedAccounts.add(caller.getId()); + } + } + + } + + } + + @Override + public void buildACLViewSearchBuilder(SearchBuilder<? extends ControlledViewEntity> sb, Long domainId, boolean isRecursive, List<Long> permittedAccounts, + ListProjectResourcesCriteria listProjectResourcesCriteria) { + + sb.and("accountIdIN", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ); + + if (((permittedAccounts.isEmpty()) && (domainId != null) && isRecursive)) { + // if accountId isn't specified, we can do a domain match for the + // admin case if isRecursive is true + sb.and("domainPath", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE); + } + + if (listProjectResourcesCriteria != null) { + if (listProjectResourcesCriteria == Project.ListProjectResourcesCriteria.ListProjectResourcesOnly) { + sb.and("accountType", sb.entity().getAccountType(), SearchCriteria.Op.EQ); + } else if (listProjectResourcesCriteria == Project.ListProjectResourcesCriteria.SkipProjectResources) { + sb.and("accountType", sb.entity().getAccountType(), SearchCriteria.Op.NEQ); + } + } + + } + + @Override + public void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> sc, Long domainId, boolean isRecursive, List<Long> permittedAccounts, + ListProjectResourcesCriteria listProjectResourcesCriteria) { + if (listProjectResourcesCriteria != null) { + sc.setParameters("accountType", Account.ACCOUNT_TYPE_PROJECT); + } + + if (!permittedAccounts.isEmpty()) { + sc.setParameters("accountIdIN", permittedAccounts.toArray()); + } else if (domainId != null) { + DomainVO domain = _domainDao.findById(domainId); + if (isRecursive) { + sc.setParameters("domainPath", domain.getPath() + "%"); + } else { + sc.setParameters("domainId", domainId); + } + } + + } + + @Override + public UserAccount getUserByApiKey(String apiKey) { + return _userAccountDao.getUserByApiKey(apiKey); + } + + @Override + public List<String> listAclGroupsByAccount(Long accountId) { + if (_querySelectors == null || _querySelectors.size() == 0) { + return new ArrayList<String>(); + } + + QuerySelector qs = _querySelectors.get(0); + return qs.listAclGroupsByAccount(accountId); + } + + @Override + public Long finalyzeAccountId(final String accountName, final Long domainId, final Long projectId, final boolean enabledOnly) { + if (accountName != null) { + if (domainId == null) { + throw new InvalidParameterValueException("Account must be specified with domainId parameter"); + } + + final Domain domain = _domainMgr.getDomain(domainId); + if (domain == null) { + throw new InvalidParameterValueException("Unable to find domain by id"); + } + + final Account account = getActiveAccountByName(accountName, domainId); + if (account != null && account.getType() != Account.ACCOUNT_TYPE_PROJECT) { + if (!enabledOnly || account.getState() == Account.State.enabled) { + return account.getId(); + } else { + throw new PermissionDeniedException("Can't add resources to the account id=" + account.getId() + " in state=" + account.getState() + " as it's no longer active"); + } + } else { + // idList is not used anywhere, so removed it now + // List<IdentityProxy> idList = new ArrayList<IdentityProxy>(); + // idList.add(new IdentityProxy("domain", domainId, "domainId")); + throw new InvalidParameterValueException("Unable to find account by name " + accountName + " in domain with specified id"); + } + } + + if (projectId != null) { + final Project project = _projectMgr.getProject(projectId); + if (project != null) { + if (!enabledOnly || project.getState() == Project.State.Active) { + return project.getProjectAccountId(); + } else { + final PermissionDeniedException ex = new PermissionDeniedException( + "Can't add resources to the project with specified projectId in state=" + project.getState() + " as it's no longer active"); + ex.addProxyObject(project.getUuid(), "projectId"); + throw ex; + } + } else { + throw new InvalidParameterValueException("Unable to find project by id"); + } + } + return null; + } + + @Override + public UserAccount getUserAccountById(Long userId) { + return _userAccountDao.findById(userId); + } + + @Override + public void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(account, so)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + account + " to " + so + " by " + checker.getName()); + } + return; + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + so); + } + + @Override + public void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(account, dof)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + account + " to " + dof + " by " + checker.getName()); + } + return; + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + dof); + } + + @Override + public void checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(user, entity)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + user + "to " + entity + "by " + checker.getName()); + } + return; + } + } + throw new PermissionDeniedException("There's no way to confirm " + user + " has access to " + entity); + } + + @Override + public String getConfigComponentName() { + return AccountManager.class.getSimpleName(); + } + + @Override + public ConfigKey<?>[] getConfigKeys() { + return new ConfigKey<?>[] {UseSecretKeyInResponse}; + } +}
