| // 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.projects; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.security.SecureRandom; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.TimeZone; |
| import java.util.UUID; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import javax.inject.Inject; |
| import javax.mail.MessagingException; |
| import javax.naming.ConfigurationException; |
| |
| import org.apache.cloudstack.acl.ProjectRole; |
| import org.apache.cloudstack.acl.SecurityChecker.AccessType; |
| import org.apache.cloudstack.acl.dao.ProjectRoleDao; |
| import org.apache.cloudstack.context.CallContext; |
| import org.apache.cloudstack.framework.config.ConfigKey; |
| import org.apache.cloudstack.framework.config.Configurable; |
| 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.utils.mailing.MailAddress; |
| import org.apache.cloudstack.utils.mailing.SMTPMailProperties; |
| import org.apache.cloudstack.utils.mailing.SMTPMailSender; |
| import org.apache.commons.lang3.BooleanUtils; |
| import org.springframework.stereotype.Component; |
| |
| import com.cloud.api.ApiDBUtils; |
| import com.cloud.api.query.dao.ProjectAccountJoinDao; |
| import com.cloud.api.query.dao.ProjectInvitationJoinDao; |
| import com.cloud.api.query.dao.ProjectJoinDao; |
| import com.cloud.configuration.Config; |
| import com.cloud.configuration.ConfigurationManager; |
| import com.cloud.configuration.Resource.ResourceType; |
| import com.cloud.domain.DomainVO; |
| import com.cloud.domain.dao.DomainDao; |
| import com.cloud.event.ActionEvent; |
| import com.cloud.event.EventTypes; |
| import com.cloud.exception.ConcurrentOperationException; |
| import com.cloud.exception.InvalidParameterValueException; |
| import com.cloud.exception.PermissionDeniedException; |
| import com.cloud.exception.ResourceAllocationException; |
| import com.cloud.exception.ResourceUnavailableException; |
| import com.cloud.network.dao.NetworkDao; |
| import com.cloud.network.dao.NetworkVO; |
| import com.cloud.network.vpc.Vpc; |
| import com.cloud.network.vpc.VpcManager; |
| import com.cloud.projects.Project.State; |
| import com.cloud.projects.ProjectAccount.Role; |
| import com.cloud.projects.dao.ProjectAccountDao; |
| import com.cloud.projects.dao.ProjectDao; |
| import com.cloud.projects.dao.ProjectInvitationDao; |
| import com.cloud.storage.VMTemplateVO; |
| import com.cloud.storage.VolumeVO; |
| import com.cloud.storage.dao.VMTemplateDao; |
| import com.cloud.storage.dao.VolumeDao; |
| import com.cloud.tags.dao.ResourceTagDao; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountManager; |
| import com.cloud.user.AccountVO; |
| import com.cloud.user.DomainManager; |
| import com.cloud.user.ResourceLimitService; |
| import com.cloud.user.User; |
| import com.cloud.user.dao.AccountDao; |
| import com.cloud.user.dao.UserDao; |
| import com.cloud.utils.component.ManagerBase; |
| import com.cloud.utils.concurrency.NamedThreadFactory; |
| import com.cloud.utils.db.DB; |
| import com.cloud.utils.db.Transaction; |
| import com.cloud.utils.db.TransactionCallback; |
| import com.cloud.utils.db.TransactionCallbackNoReturn; |
| import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; |
| import com.cloud.utils.db.TransactionStatus; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.vm.UserVmVO; |
| import com.cloud.vm.dao.UserVmDao; |
| import com.cloud.vm.snapshot.VMSnapshotVO; |
| import com.cloud.vm.snapshot.dao.VMSnapshotDao; |
| |
| @Component |
| public class ProjectManagerImpl extends ManagerBase implements ProjectManager, Configurable { |
| |
| private static final SecureRandom secureRandom = new SecureRandom(); |
| |
| @Inject |
| private DomainDao _domainDao; |
| @Inject |
| private ProjectDao _projectDao; |
| @Inject |
| private ProjectJoinDao _projectJoinDao; |
| @Inject |
| AccountManager _accountMgr; |
| @Inject |
| DomainManager _domainMgr; |
| @Inject |
| ConfigurationManager _configMgr; |
| @Inject |
| ResourceLimitService _resourceLimitMgr; |
| @Inject |
| private ProjectAccountDao _projectAccountDao; |
| @Inject |
| private ProjectAccountJoinDao _projectAccountJoinDao; |
| @Inject |
| private AccountDao _accountDao; |
| @Inject |
| private ConfigurationDao _configDao; |
| @Inject |
| private ProjectInvitationDao _projectInvitationDao; |
| @Inject |
| private ProjectInvitationJoinDao _projectInvitationJoinDao; |
| @Inject |
| protected ResourceTagDao _resourceTagDao; |
| @Inject |
| private ProjectRoleDao projectRoleDao; |
| @Inject |
| private UserDao userDao; |
| @Inject |
| private VolumeDao _volumeDao; |
| @Inject |
| private UserVmDao _userVmDao; |
| @Inject |
| private VMTemplateDao _templateDao; |
| @Inject |
| private NetworkDao _networkDao; |
| @Inject |
| private VMSnapshotDao _vmSnapshotDao; |
| @Inject |
| private VpcManager _vpcMgr; |
| @Inject |
| MessageBus messageBus; |
| |
| protected boolean _invitationRequired = false; |
| protected long _invitationTimeOut = 86400000; |
| protected boolean _allowUserToCreateProject = true; |
| protected ScheduledExecutorService _executor; |
| protected int _projectCleanupExpInvInterval = 60; //Interval defining how often project invitation cleanup thread is running |
| private String senderAddress; |
| protected SMTPMailSender mailSender; |
| |
| @Override |
| public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { |
| |
| Map<String, String> configs = _configDao.getConfiguration(params); |
| _invitationRequired = BooleanUtils.toBoolean(configs.get(Config.ProjectInviteRequired.key())); |
| |
| String value = configs.get(Config.ProjectInvitationExpirationTime.key()); |
| _invitationTimeOut = Long.parseLong(value != null ? value : "86400") * 1000; |
| _allowUserToCreateProject = BooleanUtils.toBoolean(configs.get(Config.AllowUserToCreateProject.key())); |
| senderAddress = configs.get("project.email.sender"); |
| |
| String namespace = "project.smtp"; |
| |
| mailSender = new SMTPMailSender(configs, namespace); |
| _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Project-ExpireInvitations")); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean start() { |
| _executor.scheduleWithFixedDelay(new ExpiredInvitationsCleanup(), _projectCleanupExpInvInterval, _projectCleanupExpInvInterval, TimeUnit.SECONDS); |
| return true; |
| } |
| |
| @Override |
| public boolean stop() { |
| return true; |
| } |
| |
| private User validateUser(Long userId, Long accountId, Long domainId) { |
| User user = null; |
| if (userId != null) { |
| user = userDao.findById(userId); |
| if (user == null ) { |
| throw new InvalidParameterValueException("Invalid user ID provided"); |
| } |
| if (user.getAccountId() != accountId || _accountDao.findById(user.getAccountId()).getDomainId() != domainId) { |
| throw new InvalidParameterValueException("User doesn't belong to the specified account or domain"); |
| } |
| } |
| return user; |
| } |
| |
| private User validateUser(Long userId, Long domainId) { |
| User user = null; |
| if (userId != null) { |
| user = userDao.findById(userId); |
| if (user == null) { |
| throw new InvalidParameterValueException("Invalid user ID provided"); |
| } |
| if (_accountDao.findById(user.getAccountId()).getDomainId() != domainId) { |
| throw new InvalidParameterValueException("User doesn't belong to the specified account or domain"); |
| } |
| } |
| return user; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", create = true) |
| @DB |
| public Project createProject(final String name, final String displayText, String accountName, final Long domainId, final Long userId, final Long accountId) throws ResourceAllocationException { |
| Account caller = CallContext.current().getCallingAccount(); |
| Account owner = caller; |
| |
| //check if the user authorized to create the project |
| if (_accountMgr.isNormalUser(caller.getId()) && !_allowUserToCreateProject) { |
| throw new PermissionDeniedException("Regular user is not permitted to create a project"); |
| } |
| |
| //Verify request parameters |
| if ((accountName != null && domainId == null) || (domainId != null && accountName == null)) { |
| throw new InvalidParameterValueException("Account name and domain id must be specified together"); |
| } |
| |
| if (userId != null && (accountId == null && domainId == null)) { |
| throw new InvalidParameterValueException("Domain ID and account ID must be provided with User ID"); |
| } |
| |
| if (accountName != null) { |
| owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); |
| } |
| |
| //don't allow 2 projects with the same name inside the same domain |
| if (_projectDao.findByNameAndDomain(name, owner.getDomainId()) != null) { |
| throw new InvalidParameterValueException("Project with name " + name + " already exists in domain id=" + owner.getDomainId()); |
| } |
| |
| User user = validateUser(userId, accountId, domainId); |
| if (user != null) { |
| owner = _accountDao.findById(user.getAccountId()); |
| } |
| |
| //do resource limit check |
| _resourceLimitMgr.checkResourceLimit(owner, ResourceType.project); |
| |
| final Account ownerFinal = owner; |
| User finalUser = user; |
| Project project = Transaction.execute(new TransactionCallback<Project>() { |
| @Override |
| public Project doInTransaction(TransactionStatus status) { |
| |
| //Create an account associated with the project |
| StringBuilder acctNm = new StringBuilder("PrjAcct-"); |
| acctNm.append(name).append("-").append(ownerFinal.getDomainId()); |
| |
| Account projectAccount = _accountMgr.createAccount(acctNm.toString(), Account.Type.PROJECT, null, domainId, null, null, UUID.randomUUID().toString()); |
| |
| Project project = _projectDao.persist(new ProjectVO(name, displayText, ownerFinal.getDomainId(), projectAccount.getId())); |
| |
| //assign owner to the project |
| assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin, |
| Optional.ofNullable(finalUser).map(User::getId).orElse(null), null); |
| |
| if (project != null) { |
| CallContext.current().setEventDetails("Project id=" + project.getId()); |
| CallContext.current().putContextParameter(Project.class, project.getUuid()); |
| } |
| |
| //Increment resource count |
| _resourceLimitMgr.incrementResourceCount(ownerFinal.getId(), ResourceType.project); |
| |
| return project; |
| } |
| }); |
| |
| messageBus.publish(_name, ProjectManager.MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project); |
| |
| return project; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", async = true) |
| @DB |
| public Project enableProject(long projectId) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| ProjectVO project = getProject(projectId); |
| //verify input parameters |
| if (project == null) { |
| throw new InvalidParameterValueException("Unable to find project by id " + projectId); |
| } |
| |
| CallContext.current().setProject(project); |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| //at this point enabling project doesn't require anything, so just update the state |
| project.setState(State.Active); |
| _projectDao.update(projectId, project); |
| |
| return project; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_DELETE, eventDescription = "deleting project", async = true) |
| public boolean deleteProject(long projectId, Boolean isCleanup) { |
| CallContext ctx = CallContext.current(); |
| |
| ProjectVO project = getProject(projectId); |
| //verify input parameters |
| if (project == null) { |
| throw new InvalidParameterValueException("Unable to find project by id " + projectId); |
| } |
| |
| CallContext.current().setProject(project); |
| _accountMgr.checkAccess(ctx.getCallingAccount(), AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| if (isCleanup != null && isCleanup) { |
| return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); |
| } else { |
| List<VMTemplateVO> userTemplates = _templateDao.listByAccountId(project.getProjectAccountId()); |
| List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.listByAccountId(project.getProjectAccountId()); |
| List<UserVmVO> vms = _userVmDao.listByAccountId(project.getProjectAccountId()); |
| List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId()); |
| List<NetworkVO> networks = _networkDao.listByOwner(project.getProjectAccountId()); |
| List<? extends Vpc> vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId()); |
| |
| Optional<String> message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs) |
| .filter(entity -> !entity.isEmpty()) |
| .map(entity -> entity.size() + " " + entity.get(0).getEntityType().getSimpleName() + " to clean up") |
| .findFirst(); |
| |
| if (message.isEmpty()) { |
| return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); |
| } |
| |
| CloudRuntimeException e = new CloudRuntimeException("Can't delete the project yet because it has " + message.get()); |
| e.addProxyObject(project.getUuid(), "projectId"); |
| throw e; |
| } |
| } |
| |
| @DB |
| @Override |
| public boolean deleteProject(Account caller, long callerUserId, final ProjectVO project) { |
| //mark project as inactive first, so you can't add resources to it |
| boolean updateResult = Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| logger.debug("Marking project id=" + project.getId() + " with state " + State.Disabled + " as a part of project delete..."); |
| project.setState(State.Disabled); |
| boolean updateResult = _projectDao.update(project.getId(), project); |
| //owner can be already removed at this point, so adding the conditional check |
| List<Long> projectOwners = getProjectOwners(project.getId()); |
| if (projectOwners != null) { |
| for (Long projectOwner : projectOwners) |
| _resourceLimitMgr.decrementResourceCount(projectOwner, ResourceType.project); |
| } |
| return updateResult; |
| } |
| }); |
| |
| if (updateResult) { |
| //pass system caller when clenaup projects account |
| if (!cleanupProject(project, _accountDao.findById(Account.ACCOUNT_ID_SYSTEM), User.UID_SYSTEM)) { |
| logger.warn("Failed to cleanup project's id=" + project.getId() + " resources, not removing the project yet"); |
| return false; |
| } else { |
| //check if any Tungsten-Fabric provider exists and delete the project from Tungsten-Fabric providers |
| messageBus.publish(_name, ProjectManager.MESSAGE_DELETE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project); |
| return _projectDao.remove(project.getId()); |
| } |
| } else { |
| logger.warn("Failed to mark the project id=" + project.getId() + " with state " + State.Disabled); |
| return false; |
| } |
| } |
| |
| @DB |
| private boolean cleanupProject(final Project project, AccountVO caller, Long callerUserId) { |
| boolean result = true; |
| //Delete project's account |
| AccountVO account = _accountDao.findById(project.getProjectAccountId()); |
| logger.debug("Deleting projects " + project + " internal account id=" + account.getId() + " as a part of project cleanup..."); |
| |
| result = result && _accountMgr.deleteAccount(account, callerUserId, caller); |
| |
| if (result) { |
| //Unassign all users from the project |
| result = Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| boolean result = true; |
| logger.debug("Unassigning all accounts from project " + project + " as a part of project cleanup..."); |
| List<? extends ProjectAccount> projectAccounts = _projectAccountDao.listByProjectId(project.getId()); |
| for (ProjectAccount projectAccount : projectAccounts) { |
| result = result && unassignAccountFromProject(projectAccount.getProjectId(), projectAccount.getAccountId()); |
| } |
| |
| logger.debug("Removing all invitations for the project " + project + " as a part of project cleanup..."); |
| _projectInvitationDao.cleanupInvitations(project.getId()); |
| return result; |
| } |
| }); |
| if (result) { |
| logger.debug("Accounts are unassign successfully from project " + project + " as a part of project cleanup..."); |
| } |
| } else { |
| logger.warn("Failed to cleanup project's internal account"); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public boolean unassignAccountFromProject(long projectId, long accountId) { |
| ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountId); |
| if (projectAccount == null) { |
| logger.debug("Account id=" + accountId + " is not assigned to project id=" + projectId + " so no need to unassign"); |
| return true; |
| } |
| |
| if (_projectAccountDao.remove(projectAccount.getId())) { |
| return true; |
| } else { |
| logger.warn("Failed to unassign account id=" + accountId + " from the project id=" + projectId); |
| return false; |
| } |
| } |
| |
| @Override |
| public ProjectVO getProject(long projectId) { |
| return _projectDao.findById(projectId); |
| } |
| |
| @Override |
| public long getInvitationTimeout() { |
| return _invitationTimeOut; |
| } |
| |
| @Override |
| public ProjectAccount assignAccountToProject(Project project, long accountId, ProjectAccount.Role accountRole, Long userId, Long projectRoleId) { |
| ProjectAccountVO projectAccountVO = new ProjectAccountVO(project, accountId, accountRole, userId, projectRoleId); |
| return _projectAccountDao.persist(projectAccountVO); |
| } |
| |
| public ProjectAccount assignUserToProject(Project project, long userId, long accountId, Role userRole, Long projectRoleId) { |
| return assignAccountToProject(project, accountId, userRole, userId, projectRoleId); |
| } |
| |
| @Override |
| @DB |
| public boolean deleteAccountFromProject(final long projectId, final long accountId) { |
| return Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| boolean success = true; |
| |
| //remove account |
| ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountId); |
| success = _projectAccountDao.remove(projectAccount.getId()); |
| |
| //remove all invitations for account |
| if (success) { |
| logger.debug("Removed account " + accountId + " from project " + projectId + " , cleaning up old invitations for account/project..."); |
| ProjectInvitation invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId); |
| if (invite != null) { |
| success = success && _projectInvitationDao.remove(invite.getId()); |
| } |
| } |
| |
| return success; |
| } |
| }); |
| } |
| |
| @Override |
| public Account getProjectOwner(long projectId) { |
| ProjectAccount prAcct = _projectAccountDao.getProjectOwner(projectId); |
| if (prAcct != null) { |
| return _accountMgr.getAccount(prAcct.getAccountId()); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public List<Long> getProjectOwners(long projectId) { |
| List<? extends ProjectAccount> projectAccounts = _projectAccountDao.getProjectOwners(projectId); |
| if (projectAccounts != null || !projectAccounts.isEmpty()) { |
| return projectAccounts.stream().map(acc -> _accountMgr.getAccount(acc.getAccountId()).getId()).collect(Collectors.toList()); |
| } |
| return null; |
| } |
| |
| @Override |
| public ProjectVO findByProjectAccountId(long projectAccountId) { |
| return _projectDao.findByProjectAccountId(projectAccountId); |
| } |
| |
| @Override |
| public ProjectVO findByProjectAccountIdIncludingRemoved(long projectAccountId) { |
| return _projectDao.findByProjectAccountIdIncludingRemoved(projectAccountId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_USER_ADD, eventDescription = "adding user to project", async = true) |
| public boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| Project project = getProject(projectId); |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| if (project.getState() != State.Active) { |
| InvalidParameterValueException ex = |
| new InvalidParameterValueException("Can't add user to the specified project id in state=" + project.getState() + " as it isn't currently active"); |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| |
| |
| User user = userDao.getUserByName(username, project.getDomainId()); |
| if (user == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Invalid user ID provided"); |
| ex.addProxyObject(String.valueOf(username), "userId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| Account userAccount = _accountDao.findById(user.getAccountId()); |
| if (_projectAccountDao.findByProjectIdAccountId(projectId, userAccount.getAccountId()) != null) { |
| throw new InvalidParameterValueException("User belongs to account " + userAccount.getAccountId() + " which is already part of the project"); |
| } |
| |
| ProjectAccount projectAccountUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); |
| if (projectAccountUser != null) { |
| logger.info("User with id: " + user.getId() + " is already added to the project with id: " + projectId); |
| return true; |
| } |
| |
| if (projectRoleId != null && projectRoleId < 1L) { |
| throw new InvalidParameterValueException("Invalid project role id provided"); |
| } |
| |
| ProjectRole role = null; |
| if (projectRoleId != null) { |
| role = projectRoleDao.findById(projectRoleId); |
| if (role == null || !role.getProjectId().equals(projectId)) { |
| throw new InvalidParameterValueException("Invalid project role ID for the given project"); |
| } |
| } |
| |
| if (_invitationRequired) { |
| return inviteUserToProject(project, user, email, projectRole, role); |
| } else { |
| if (username == null) { |
| throw new InvalidParameterValueException("User information (ID) is required to add user to the project"); |
| } |
| if (assignUserToProject(project, user.getId(), user.getAccountId(), projectRole, |
| Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } |
| logger.warn("Failed to add user to project with id: " + projectId); |
| return false; |
| } |
| } |
| |
| @Override |
| public Project findByNameAndDomainId(String name, long domainId) { |
| return _projectDao.findByNameAndDomain(name, domainId); |
| } |
| |
| @Override |
| public boolean canAccessProjectAccount(Account caller, long accountId) { |
| //ROOT admin always can access the project |
| if (_accountMgr.isRootAdmin(caller.getId())) { |
| return true; |
| } else if (_accountMgr.isDomainAdmin(caller.getId())) { |
| Account owner = _accountMgr.getAccount(accountId); |
| _accountMgr.checkAccess(caller, _domainDao.findById(owner.getDomainId())); |
| return true; |
| } |
| User user = CallContext.current().getCallingUser(); |
| ProjectVO project = _projectDao.findByProjectAccountId(accountId); |
| if (project != null) { |
| ProjectAccount userProjectAccount = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); |
| if (userProjectAccount != null) { |
| return _projectAccountDao.canUserAccessProjectAccount(user.getAccountId(), user.getId(), accountId); |
| } |
| } |
| return _projectAccountDao.canAccessProjectAccount(caller.getId(), accountId); |
| } |
| |
| @Override |
| public boolean canModifyProjectAccount(Account caller, long accountId) { |
| //ROOT admin always can access the project |
| if (_accountMgr.isRootAdmin(caller.getId())) { |
| return true; |
| } else if (_accountMgr.isDomainAdmin(caller.getId())) { |
| Account owner = _accountMgr.getAccount(accountId); |
| _accountMgr.checkAccess(caller, _domainDao.findById(owner.getDomainId())); |
| return true; |
| } |
| |
| User user = CallContext.current().getCallingUser(); |
| Project project = CallContext.current().getProject(); |
| if (project != null) { |
| ProjectAccountVO projectUser = _projectAccountDao.findByProjectIdUserId(project.getId(), caller.getAccountId(), user.getId()); |
| if (projectUser != null) { |
| return _projectAccountDao.canUserModifyProject(project.getId(), caller.getAccountId(), user.getId()); |
| } |
| } |
| return _projectAccountDao.canModifyProjectAccount(caller.getId(), accountId); |
| } |
| |
| private void updateProjectAccount(ProjectAccountVO futureOwner, Role newAccRole, Long accountId) throws ResourceAllocationException { |
| _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(accountId), ResourceType.project); |
| futureOwner.setAccountRole(newAccRole); |
| _projectAccountDao.update(futureOwner.getId(), futureOwner); |
| if (newAccRole != null && Role.Admin == newAccRole) { |
| _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project); |
| } else { |
| _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project); |
| } |
| } |
| |
| @Override |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true) |
| public Project updateProject(final long projectId, String name, final String displayText, final String newOwnerName) throws ResourceAllocationException { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| //check that the project exists |
| final ProjectVO project = getProject(projectId); |
| |
| if (project == null) { |
| throw new InvalidParameterValueException("Unable to find the project id=" + projectId); |
| } |
| |
| //verify permissions |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| Transaction.execute(new TransactionCallbackWithExceptionNoReturn<ResourceAllocationException>() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { |
| updateProjectNameAndDisplayText(project, name, displayText); |
| |
| if (newOwnerName != null) { |
| //check that the new owner exists |
| Account futureOwnerAccount = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); |
| if (futureOwnerAccount == null) { |
| throw new InvalidParameterValueException("Unable to find account name=" + newOwnerName + " in domain id=" + project.getDomainId()); |
| } |
| Account currentOwnerAccount = getProjectOwner(projectId); |
| if (currentOwnerAccount == null) { |
| logger.error("Unable to find the current owner for the project id=" + projectId); |
| throw new InvalidParameterValueException("Unable to find the current owner for the project id=" + projectId); |
| } |
| if (currentOwnerAccount.getId() != futureOwnerAccount.getId()) { |
| ProjectAccountVO futureOwner = _projectAccountDao.findByProjectIdAccountId(projectId, futureOwnerAccount.getAccountId()); |
| if (futureOwner == null) { |
| throw new InvalidParameterValueException("Account " + newOwnerName + |
| " doesn't belong to the project. Add it to the project first and then change the project's ownership"); |
| } |
| |
| //do resource limit check |
| _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(futureOwnerAccount.getId()), ResourceType.project); |
| |
| //unset the role for the old owner |
| ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId()); |
| currentOwner.setAccountRole(Role.Regular); |
| _projectAccountDao.update(currentOwner.getId(), currentOwner); |
| _resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project); |
| |
| //set new owner |
| futureOwner.setAccountRole(Role.Admin); |
| _projectAccountDao.update(futureOwner.getId(), futureOwner); |
| _resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project); |
| |
| } else { |
| logger.trace("Future owner " + newOwnerName + "is already the owner of the project id=" + projectId); |
| } |
| } |
| } |
| }); |
| |
| return _projectDao.findById(projectId); |
| |
| } |
| |
| @Override |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true) |
| public Project updateProject(final long projectId, String name, final String displayText, final String newOwnerName, Long userId, |
| Role newRole) throws ResourceAllocationException { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| //check that the project exists |
| final ProjectVO project = getProject(projectId); |
| |
| if (project == null) { |
| throw new InvalidParameterValueException("Unable to find the project id=" + projectId); |
| } |
| |
| CallContext.current().setProject(project); |
| //verify permissions |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| List<? extends ProjectAccount> projectOwners = _projectAccountDao.getProjectOwners(projectId); |
| |
| Transaction.execute(new TransactionCallbackWithExceptionNoReturn<ResourceAllocationException>() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { |
| updateProjectNameAndDisplayText(project, name, displayText); |
| |
| if (newOwnerName != null) { |
| //check that the new owner exists |
| Account updatedAcc = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); |
| if (updatedAcc == null) { |
| throw new InvalidParameterValueException("Unable to find account name=" + newOwnerName + " in domain id=" + project.getDomainId()); |
| } |
| ProjectAccountVO newProjectAcc = _projectAccountDao.findByProjectIdAccountId(projectId, updatedAcc.getAccountId()); |
| if (newProjectAcc == null) { |
| throw new InvalidParameterValueException("Account " + newOwnerName + |
| " doesn't belong to the project. Add it to the project first and then change the project's ownership"); |
| } |
| |
| if (isTheOnlyProjectOwner(projectId, newProjectAcc, caller) && newRole != Role.Admin) { |
| throw new InvalidParameterValueException("Cannot demote the only admin of the project"); |
| } |
| updateProjectAccount(newProjectAcc, newRole, updatedAcc.getId()); |
| } else if (userId != null) { |
| User user = validateUser(userId, project.getDomainId()); |
| if (user == null) { |
| throw new InvalidParameterValueException("Unable to find user= " + user.getUsername() + " in domain id = " + project.getDomainId()); |
| } |
| ProjectAccountVO newProjectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), userId); |
| if (newProjectUser == null) { |
| throw new InvalidParameterValueException("User " + userId + |
| " doesn't belong to the project. Add it to the project first and then change the project's ownership"); |
| } |
| |
| if (projectOwners.size() == 1 && newProjectUser.getUserId().equals(projectOwners.get(0).getUserId()) |
| && newRole != Role.Admin ) { |
| throw new InvalidParameterValueException("Cannot demote the only admin of the project"); |
| } |
| |
| updateProjectAccount(newProjectUser, newRole, user.getAccountId()); |
| |
| } |
| } |
| }); |
| return _projectDao.findById(projectId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_ADD, eventDescription = "adding account to project", async = true) |
| public boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| //check that the project exists |
| Project project = getProject(projectId); |
| |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| //User can be added to Active project only |
| if (project.getState() != Project.State.Active) { |
| InvalidParameterValueException ex = |
| new InvalidParameterValueException("Can't add account to the specified project id in state=" + project.getState() + " as it's no longer active"); |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| |
| //check that account-to-add exists |
| Account account = null; |
| if (accountName != null) { |
| account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); |
| if (account == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find account name=" + accountName + " in specified domain id"); |
| DomainVO domain = ApiDBUtils.findDomainById(project.getDomainId()); |
| String domainUuid = String.valueOf(project.getDomainId()); |
| if (domain != null) { |
| domainUuid = domain.getUuid(); |
| } |
| ex.addProxyObject(domainUuid, "domainId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| //verify permissions - only project owner can assign |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| //Check if the account already added to the project |
| ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); |
| if (projectAccount != null) { |
| logger.debug("Account " + accountName + " already added to the project id=" + projectId); |
| return true; |
| } |
| } |
| |
| if (projectRoleId != null && projectRoleId < 1L) { |
| throw new InvalidParameterValueException("Invalid project role id provided"); |
| } |
| |
| ProjectRole projectRole = null; |
| if (projectRoleId != null) { |
| projectRole = projectRoleDao.findById(projectRoleId); |
| if (projectRole == null || projectRole.getProjectId() != projectId) { |
| throw new InvalidParameterValueException("Invalid project role ID for the given project"); |
| } |
| } |
| |
| if (_invitationRequired) { |
| return inviteAccountToProject(project, account, email, projectRoleType, projectRole); |
| } else { |
| if (account == null) { |
| throw new InvalidParameterValueException("Account information is required for assigning account to the project"); |
| } |
| if (assignAccountToProject(project, account.getId(), projectRoleType, null, |
| Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } else { |
| logger.warn("Failed to add account " + accountName + " to project id=" + projectId); |
| return false; |
| } |
| } |
| } |
| |
| private boolean inviteAccountToProject(Project project, Account account, String email, Role role,ProjectRole projectRole) { |
| if (account != null) { |
| if (createAccountInvitation(project, account.getId(), null, role, |
| Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } else { |
| logger.warn("Failed to generate invitation for account " + account.getAccountName() + " to project id=" + project); |
| return false; |
| } |
| } |
| |
| if (email != null) { |
| //generate the token |
| String token = generateToken(10); |
| if (generateTokenBasedInvitation(project, null, email, token, role, |
| Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } else { |
| logger.warn("Failed to generate invitation for email " + email + " to project id=" + project); |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean inviteUserToProject(Project project, User user, String email, Role role, ProjectRole projectRole) { |
| if (email == null) { |
| if (createAccountInvitation(project, user.getAccountId(), user.getId(), role, |
| Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } else { |
| logger.warn("Failed to generate invitation for account " + user.getUsername() + " to project id=" + project); |
| return false; |
| } |
| } else { |
| //generate the token |
| String token = generateToken(10); |
| if (generateTokenBasedInvitation(project, user.getId(), email, token, role, |
| Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { |
| return true; |
| } else { |
| logger.warn("Failed to generate invitation for email " + email + " to project id=" + project); |
| return false; |
| } |
| } |
| } |
| |
| private boolean isTheOnlyProjectOwner(Long projectId, ProjectAccount projectAccount, Account caller) { |
| List<? extends ProjectAccount> projectOwners = _projectAccountDao.getProjectOwners(projectId); |
| if ((projectOwners.size() == 1 && projectOwners.get(0).getAccountId() == projectAccount.getAccountId() |
| && projectAccount.getAccountRole() == Role.Admin )) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_REMOVE, eventDescription = "removing account from project", async = true) |
| public boolean deleteAccountFromProject(long projectId, String accountName) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| //check that the project exists |
| Project project = getProject(projectId); |
| |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| //check that account-to-remove exists |
| Account account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); |
| if (account == null) { |
| InvalidParameterValueException ex = |
| new InvalidParameterValueException("Unable to find account name=" + accountName + " in domain id=" + project.getDomainId()); |
| DomainVO domain = ApiDBUtils.findDomainById(project.getDomainId()); |
| String domainUuid = String.valueOf(project.getDomainId()); |
| if (domain != null) { |
| domainUuid = domain.getUuid(); |
| } |
| ex.addProxyObject(domainUuid, "domainId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| //verify permissions |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| //Check if the account exists in the project |
| ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); |
| if (projectAccount == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Account " + accountName + " is not assigned to the project with specified id"); |
| // Use the projectVO object and not the projectAccount object to inject the projectId. |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| |
| //can't remove the owner of the project |
| if (isTheOnlyProjectOwner(projectId, projectAccount, caller)) { |
| InvalidParameterValueException ex = |
| new InvalidParameterValueException("Unable to delete account " + accountName + |
| " from the project with specified id as the account is the owner of the project"); |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| |
| return deleteAccountFromProject(projectId, account.getId()); |
| } |
| |
| @Override |
| public boolean deleteUserFromProject(long projectId, long userId) { |
| Account caller = CallContext.current().getCallingAccount(); |
| //check that the project exists |
| Project project = getProject(projectId); |
| |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| User user = userDao.findById(userId); |
| if (user == null) { |
| throw new InvalidParameterValueException("Invalid userId provided"); |
| } |
| Account userAcc = _accountDao.findActiveAccountById(user.getAccountId(), project.getDomainId()); |
| if (userAcc == null) { |
| InvalidParameterValueException ex = |
| new InvalidParameterValueException("Unable to find user "+ user.getUsername() + " in domain id=" + project.getDomainId()); |
| DomainVO domain = ApiDBUtils.findDomainById(project.getDomainId()); |
| String domainUuid = String.valueOf(project.getDomainId()); |
| if (domain != null) { |
| domainUuid = domain.getUuid(); |
| } |
| ex.addProxyObject(domainUuid, "domainId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| //verify permissions |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| //Check if the user exists in the project |
| ProjectAccount projectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); |
| if (projectUser == null) { |
| deletePendingInvite(projectId, user); |
| InvalidParameterValueException ex = new InvalidParameterValueException("User " + user.getUsername() + " is not assigned to the project with specified id"); |
| // Use the projectVO object and not the projectAccount object to inject the projectId. |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| return deleteUserFromProject(projectId, user); |
| } |
| |
| private void deletePendingInvite(Long projectId, User user) { |
| ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId); |
| if (invite != null) { |
| boolean success = _projectInvitationDao.remove(invite.getId()); |
| if (success){ |
| logger.info("Successfully deleted invite pending for the user : "+user.getUsername()); |
| } else { |
| logger.info("Failed to delete project invite for user: "+ user.getUsername()); |
| } |
| } |
| } |
| |
| @DB |
| private boolean deleteUserFromProject(Long projectId, User user) { |
| return Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| boolean success = true; |
| ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); |
| success = _projectAccountDao.remove(projectAccount.getId()); |
| |
| if (success) { |
| logger.debug("Removed user " + user.getId() + " from project. Removing any invite sent to the user"); |
| ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId); |
| if (invite != null) { |
| success = success && _projectInvitationDao.remove(invite.getId()); |
| } |
| } |
| return success; |
| } |
| }); |
| } |
| |
| public ProjectInvitation createAccountInvitation(Project project, Long accountId, Long userId, Role role, Long projectRoleId) { |
| if (activeInviteExists(project, accountId, userId, null)) { |
| throw new InvalidParameterValueException("There is already a pending invitation for account id=" + accountId + " to the project id=" + project); |
| } |
| |
| ProjectInvitationVO invitationVO = new ProjectInvitationVO(project.getId(), accountId, project.getDomainId(), null, null); |
| if (userId != null) { |
| invitationVO.setForUserId(userId); |
| } |
| if (role != null) { |
| invitationVO.setAccountRole(role); |
| } |
| if (projectRoleId != null) { |
| invitationVO.setProjectRoleId(projectRoleId); |
| } |
| return _projectInvitationDao.persist(invitationVO); |
| } |
| |
| @DB |
| public boolean activeInviteExists(final Project project, final Long accountId, Long userId, final String email) { |
| return Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| //verify if the invitation was already generated |
| ProjectInvitationVO invite = null; |
| if (accountId != null) { |
| invite = _projectInvitationDao.findByAccountIdProjectId(accountId, project.getId()); |
| } else if (userId != null) { |
| invite = _projectInvitationDao.findByUserIdProjectId(userId, accountId, project.getId()); |
| } else if (email != null) { |
| invite = _projectInvitationDao.findByEmailAndProjectId(email, project.getId()); |
| } |
| |
| if (invite != null) { |
| if (invite.getState() == ProjectInvitation.State.Completed || |
| (invite.getState() == ProjectInvitation.State.Pending && _projectInvitationDao.isActive(invite.getId(), _invitationTimeOut))) { |
| return true; |
| } else { |
| if (invite.getState() == ProjectInvitation.State.Pending) { |
| expireInvitation(invite); |
| } |
| //remove the expired/declined invitation |
| if (accountId != null) { |
| logger.debug("Removing invitation in state " + invite.getState() + " for account id=" + accountId + " to project " + project); |
| } else if (userId != null) { |
| logger.debug("Removing invitation in state " + invite.getState() + " for user id=" + userId + " to project " + project); |
| } else if (email != null) { |
| logger.debug("Removing invitation in state " + invite.getState() + " for email " + email + " to project " + project); |
| } |
| |
| _projectInvitationDao.expunge(invite.getId()); |
| } |
| } |
| |
| return false; |
| } |
| }); |
| } |
| |
| public ProjectInvitation generateTokenBasedInvitation(Project project, Long userId, String email, String token, Role role, Long projectRoleId) { |
| //verify if the invitation was already generated |
| if (activeInviteExists(project, null, null, email)) { |
| throw new InvalidParameterValueException("There is already a pending invitation for email " + email + " to the project id=" + project); |
| } |
| |
| ProjectInvitationVO projectInvitationVO = new ProjectInvitationVO(project.getId(), null, project.getDomainId(), email, token); |
| if (userId != null) { |
| projectInvitationVO.setForUserId(userId); |
| } |
| if (role != null) { |
| projectInvitationVO.setAccountRole(role); |
| } |
| if (projectRoleId != null) { |
| projectInvitationVO.setProjectRoleId(projectRoleId); |
| } |
| |
| ProjectInvitation projectInvitation = _projectInvitationDao.persist(projectInvitationVO); |
| try { |
| sendInvite(token, email, project.getId()); |
| } catch (Exception ex) { |
| logger.warn("Failed to send project id=" + project + " invitation to the email " + email + "; removing the invitation record from the db", ex); |
| _projectInvitationDao.remove(projectInvitation.getId()); |
| return null; |
| } |
| |
| return projectInvitation; |
| } |
| |
| protected void sendInvite(String token, String email, long projectId) throws MessagingException, UnsupportedEncodingException { |
| String subject = String.format("You are invited to join the cloud stack project id=[%s].", projectId); |
| String content = String.format("You've been invited to join the CloudStack project id=[%s]. Please use token [%s] to complete registration", projectId, token); |
| |
| SMTPMailProperties mailProperties = new SMTPMailProperties(); |
| |
| mailProperties.setSender(new MailAddress(senderAddress)); |
| mailProperties.setSubject(subject); |
| mailProperties.setContent(content); |
| mailProperties.setContentType("text/plain"); |
| |
| Set<MailAddress> addresses = new HashSet<>(); |
| |
| addresses.add(new MailAddress(email)); |
| |
| mailProperties.setRecipients(addresses); |
| |
| mailSender.sendMail(mailProperties); |
| |
| } |
| |
| private boolean expireInvitation(ProjectInvitationVO invite) { |
| logger.debug("Expiring invitation id=" + invite.getId()); |
| invite.setState(ProjectInvitation.State.Expired); |
| return _projectInvitationDao.update(invite.getId(), invite); |
| } |
| |
| @Override |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_INVITATION_UPDATE, eventDescription = "updating project invitation", async = true) |
| public boolean updateInvitation(final long projectId, String accountName, Long userId, String token, final boolean accept) { |
| Account caller = CallContext.current().getCallingAccount(); |
| Long accountId = null; |
| User user = null; |
| boolean result = true; |
| |
| //check that the project exists |
| final Project project = getProject(projectId); |
| |
| if (project == null) { |
| throw new InvalidParameterValueException("Unable to find the project id=" + projectId); |
| } |
| CallContext.current().setProject(project); |
| if (accountName != null) { |
| //check that account-to-remove exists |
| Account account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); |
| if (account == null) { |
| throw new InvalidParameterValueException("Unable to find account name=" + accountName + " in domain id=" + project.getDomainId()); |
| } |
| |
| //verify permissions |
| _accountMgr.checkAccess(caller, null, true, account); |
| accountId = account.getId(); |
| } else if (userId != null) { |
| user = userDao.findById(userId); |
| if (user == null) { |
| throw new InvalidParameterValueException("Invalid user ID provided. Please provide a valid user ID or " + |
| "account name whose invitation is to be updated"); |
| } |
| Account userAccount = _accountDao.findById(user.getAccountId()); |
| if (userAccount.getDomainId() != project.getDomainId()) { |
| throw new InvalidParameterValueException("Unable to find user =" + userId + " in domain id=" + project.getDomainId()); |
| } |
| } else { |
| accountId = caller.getId(); |
| user = CallContext.current().getCallingUser(); |
| } |
| |
| //check that invitation exists |
| ProjectInvitationVO invite = null; |
| if (token == null) { |
| if (accountName != null) { |
| invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); |
| } else { |
| invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId, ProjectInvitation.State.Pending); |
| } |
| } else { |
| invite = _projectInvitationDao.findPendingByTokenAndProjectId(token, projectId, ProjectInvitation.State.Pending); |
| } |
| |
| if (invite != null) { |
| if (!_projectInvitationDao.isActive(invite.getId(), _invitationTimeOut) && accept) { |
| expireInvitation(invite); |
| throw new InvalidParameterValueException("Invitation is expired for account id=" + accountName + " to the project id=" + projectId); |
| } else { |
| final ProjectInvitationVO inviteFinal = invite; |
| final Long accountIdFinal = invite.getAccountId() != -1 ? invite.getAccountId() : accountId; |
| final String accountNameFinal = accountName; |
| final User finalUser = getFinalUser(user, invite); |
| result = Transaction.execute(new TransactionCallback<Boolean>() { |
| @Override |
| public Boolean doInTransaction(TransactionStatus status) { |
| boolean result = true; |
| |
| ProjectInvitation.State newState = accept ? ProjectInvitation.State.Completed : ProjectInvitation.State.Declined; |
| |
| //update invitation |
| logger.debug("Marking invitation " + inviteFinal + " with state " + newState); |
| inviteFinal.setState(newState); |
| result = _projectInvitationDao.update(inviteFinal.getId(), inviteFinal); |
| |
| if (result && accept) { |
| //check if account already exists for the project (was added before invitation got accepted) |
| if (inviteFinal.getForUserId() == -1) { |
| ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountIdFinal); |
| if (projectAccount != null) { |
| logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); |
| } else { |
| assignAccountToProject(project, accountIdFinal, inviteFinal.getAccountRole(), null, inviteFinal.getProjectRoleId()); |
| } |
| } else { |
| ProjectAccount projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, finalUser.getAccountId(), finalUser.getId()); |
| if (projectAccount != null) { |
| logger.debug("User " + finalUser.getId() + "has already been added to the project id=" + projectId); |
| } else { |
| assignUserToProject(project, inviteFinal.getForUserId(), finalUser.getAccountId(), inviteFinal.getAccountRole(), inviteFinal.getProjectRoleId()); |
| } |
| } |
| } else { |
| logger.warn("Failed to update project invitation " + inviteFinal + " with state " + newState); |
| } |
| return result; |
| } |
| }); |
| } |
| } else { |
| throw new InvalidParameterValueException("Unable to find invitation for account name=" + accountName + " to the project id=" + projectId); |
| } |
| |
| return result; |
| } |
| |
| private User getFinalUser(User user, ProjectInvitationVO invite) { |
| User returnedUser = user; |
| if (invite.getForUserId() != -1 && invite.getForUserId() != user.getId()) { |
| returnedUser = userDao.getUser(invite.getForUserId()); |
| } |
| return returnedUser; |
| } |
| |
| @Override |
| public List<Long> listPermittedProjectAccounts(long accountId) { |
| return _projectAccountDao.listPermittedAccountIds(accountId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACTIVATE, eventDescription = "activating project") |
| @DB |
| public Project activateProject(final long projectId) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| //check that the project exists |
| final ProjectVO project = getProject(projectId); |
| |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| //verify permissions |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| //allow project activation only when it's in Suspended state |
| Project.State currentState = project.getState(); |
| |
| if (currentState == State.Active) { |
| logger.debug("The project id=" + projectId + " is already active, no need to activate it again"); |
| return project; |
| } |
| |
| if (currentState != State.Suspended) { |
| throw new InvalidParameterValueException("Can't activate the project in " + currentState + " state"); |
| } |
| |
| Transaction.execute(new TransactionCallbackNoReturn() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) { |
| project.setState(Project.State.Active); |
| _projectDao.update(projectId, project); |
| |
| _accountMgr.enableAccount(project.getProjectAccountId()); |
| } |
| }); |
| |
| return _projectDao.findById(projectId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_SUSPEND, eventDescription = "suspending project", async = true) |
| public Project suspendProject(long projectId) throws ConcurrentOperationException, ResourceUnavailableException { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| ProjectVO project = getProject(projectId); |
| //verify input parameters |
| if (project == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); |
| ex.addProxyObject(String.valueOf(projectId), "projectId"); |
| throw ex; |
| } |
| |
| CallContext.current().setProject(project); |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| if (suspendProject(project)) { |
| logger.debug("Successfully suspended project id=" + projectId); |
| return _projectDao.findById(projectId); |
| } else { |
| CloudRuntimeException ex = new CloudRuntimeException("Failed to suspend project with specified id"); |
| ex.addProxyObject(project.getUuid(), "projectId"); |
| throw ex; |
| } |
| |
| } |
| |
| private boolean suspendProject(ProjectVO project) throws ConcurrentOperationException, ResourceUnavailableException { |
| |
| logger.debug("Marking project " + project + " with state " + State.Suspended + " as a part of project suspend..."); |
| project.setState(State.Suspended); |
| boolean updateResult = _projectDao.update(project.getId(), project); |
| |
| if (updateResult) { |
| long projectAccountId = project.getProjectAccountId(); |
| if (!_accountMgr.disableAccount(projectAccountId)) { |
| logger.warn("Failed to suspend all project's " + project + " resources; the resources will be suspended later by background thread"); |
| } |
| } else { |
| throw new CloudRuntimeException("Failed to mark the project " + project + " with state " + State.Suspended); |
| } |
| return true; |
| } |
| |
| public static String generateToken(int length) { |
| String charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| StringBuffer sb = new StringBuffer(); |
| for (int i = 0; i < length; i++) { |
| int pos = secureRandom.nextInt(charset.length()); |
| sb.append(charset.charAt(pos)); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_PROJECT_INVITATION_REMOVE, eventDescription = "removing project invitation", async = true) |
| public boolean deleteProjectInvitation(long id) { |
| Account caller = CallContext.current().getCallingAccount(); |
| |
| ProjectInvitation invitation = _projectInvitationDao.findById(id); |
| if (invitation == null) { |
| throw new InvalidParameterValueException("Unable to find project invitation by id " + id); |
| } |
| |
| //check that the project exists |
| Project project = getProject(invitation.getProjectId()); |
| |
| CallContext.current().setProject(project); |
| //check permissions - only project owner can remove the invitations |
| _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); |
| |
| if (_projectInvitationDao.remove(id)) { |
| logger.debug("Project Invitation id=" + id + " is removed"); |
| return true; |
| } else { |
| logger.debug("Failed to remove project invitation id=" + id); |
| return false; |
| } |
| } |
| |
| public class ExpiredInvitationsCleanup extends ManagedContextRunnable { |
| @Override |
| protected void runInContext() { |
| try { |
| TimeZone.getDefault(); |
| List<ProjectInvitationVO> invitationsToExpire = _projectInvitationDao.listInvitationsToExpire(_invitationTimeOut); |
| if (!invitationsToExpire.isEmpty()) { |
| logger.debug("Found " + invitationsToExpire.size() + " projects to expire"); |
| for (ProjectInvitationVO invitationToExpire : invitationsToExpire) { |
| invitationToExpire.setState(ProjectInvitation.State.Expired); |
| _projectInvitationDao.update(invitationToExpire.getId(), invitationToExpire); |
| logger.trace("Expired project invitation id=" + invitationToExpire.getId()); |
| } |
| } |
| } catch (Exception ex) { |
| logger.warn("Exception while running expired invitations cleanup", ex); |
| } |
| } |
| } |
| |
| @Override |
| public boolean projectInviteRequired() { |
| return _invitationRequired; |
| } |
| |
| @Override |
| public boolean allowUserToCreateProject() { |
| return _allowUserToCreateProject; |
| } |
| |
| @Override |
| public String getConfigComponentName() { |
| return ProjectManager.class.getSimpleName(); |
| } |
| |
| @Override |
| public ConfigKey<?>[] getConfigKeys() { |
| return new ConfigKey<?>[] {ProjectSmtpEnabledSecurityProtocols, ProjectSmtpUseStartTLS}; |
| } |
| |
| protected void updateProjectNameAndDisplayText(final ProjectVO project, String name, String displayText) { |
| if (name == null && displayText == null){ |
| return; |
| } |
| if (name != null) { |
| project.setName(name); |
| } |
| if (displayText != null) { |
| project.setDisplayText(displayText); |
| } |
| _projectDao.update(project.getId(), project); |
| } |
| } |