Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/projects/ProjectService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.projects.ProjectAccount.Role;
import com.cloud.user.Account;
import com.cloud.user.User;

public interface ProjectService {
/**
Expand Down Expand Up @@ -102,4 +103,5 @@ public interface ProjectService {

boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) throws ResourceAllocationException;

void moveProjectAssociationsToUser(User oldUser, User newUser) throws ResourceAllocationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import javax.inject.Inject;

import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
Expand Down Expand Up @@ -112,7 +113,7 @@ public ApiCommandResourceType getApiResourceType() {
}

@Override
public void execute() {
public void execute() throws ResourceAllocationException {
Preconditions.checkNotNull(getId(),"I have to have an user to move!");
Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.List;

import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.api.command.admin.account.DeleteAccountCmd;
import org.apache.cloudstack.api.command.admin.account.DisableAccountCmd;
import org.apache.cloudstack.api.command.admin.account.EnableAccountCmd;
Expand Down Expand Up @@ -116,7 +117,7 @@ public interface RegionService {
* @param moveUserCmd
* @return true if delete was successful, false otherwise
*/
boolean moveUser(MoveUserCmd moveUserCmd);
boolean moveUser(MoveUserCmd moveUserCmd) throws ResourceAllocationException;

/**
* update an existing domain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public long getProjectAccountId() {
return projectAccountId;
}

public void setAccountId(long accountId) {
this.accountId = accountId;
}

public void setProjectRoleId(Long projectRoleId) {
this.projectRoleId = projectRoleId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ public Long getForAccountId() {
return forAccountId;
}

public void setForAccountId(Long forAccountId) {
this.forAccountId = forAccountId;
}

@Override
public String getToken() {
return token;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectAccountVO;
import com.cloud.user.User;
import com.cloud.utils.db.GenericDao;

public interface ProjectAccountDao extends GenericDao<ProjectAccountVO, Long> {
Expand Down Expand Up @@ -47,9 +48,11 @@ public interface ProjectAccountDao extends GenericDao<ProjectAccountVO, Long> {

void removeAccountFromProjects(long accountId);

void removeUserFromProjects(long userId);

boolean canUserModifyProject(long projectId, long accountId, long userId);

List<ProjectAccountVO> listUsersOrAccountsByRole(long id);

List<ProjectAccountVO> listBy(Long projectId, Long accountId, Long userId);

void move(User oldUser, User newUser);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.List;

import com.cloud.user.User;
import org.springframework.stereotype.Component;

import com.cloud.projects.ProjectAccount;
Expand Down Expand Up @@ -192,17 +193,6 @@ public void removeAccountFromProjects(long accountId) {
}
}

@Override
public void removeUserFromProjects(long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
sc.setParameters("userId", userId);

int removedCount = remove(sc);
if (removedCount > 0) {
logger.debug(String.format("Removed user [%s] from %s project(s).", userId, removedCount));
}
}

@Override
public boolean canUserModifyProject(long projectId, long accountId, long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
Expand All @@ -222,4 +212,23 @@ public List<ProjectAccountVO> listUsersOrAccountsByRole(long id) {
sc.setParameters("projectRoleId", id);
return listBy(sc);
}

@Override
public List<ProjectAccountVO> listBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
sc.setParametersIfNotNull("projectId", projectId);
sc.setParametersIfNotNull("userId", userId);
sc.setParametersIfNotNull("accountId", accountId);
return listBy(sc);
}

@Override
public void move(User oldUser, User newUser) {
List<ProjectAccountVO> projectAccounts = listBy(null, oldUser.getAccountId(), oldUser.getId());
for (ProjectAccountVO projectAccount : projectAccounts) {
projectAccount.setAccountId(newUser.getAccountId());
projectAccount.setUserId(newUser.getId());
update(projectAccount.getId(), projectAccount);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.cloud.projects.ProjectInvitation.State;
import com.cloud.projects.ProjectInvitationVO;
import com.cloud.user.User;
import com.cloud.utils.db.GenericDao;

public interface ProjectInvitationDao extends GenericDao<ProjectInvitationVO, Long> {
Expand All @@ -43,4 +44,9 @@ public interface ProjectInvitationDao extends GenericDao<ProjectInvitationVO, Lo

List<ProjectInvitationVO> listInvitationsToExpire(long timeOut);

int removeBy(Long projectId, Long accountId, Long userId);

List<ProjectInvitationVO> listBy(Long projectId, Long accountId, Long userId);

void move(User oldUser, User newUser);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.sql.Date;
import java.util.List;

import com.cloud.user.User;
import org.springframework.stereotype.Component;

import com.cloud.projects.ProjectInvitation.State;
Expand Down Expand Up @@ -124,6 +125,40 @@ public List<ProjectInvitationVO> listInvitationsToExpire(long timeOut) {
return listBy(sc);
}

@Override
public int removeBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = prepareAllFieldsSearchCriteria(projectId, accountId, userId);
return remove(sc);
}

@Override
public List<ProjectInvitationVO> listBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = prepareAllFieldsSearchCriteria(projectId, accountId, userId);
return listBy(sc);
}

@Override
public void move(User oldUser, User newUser) {
List<ProjectInvitationVO> projectInvitations = listBy(null, oldUser.getAccountId(), oldUser.getId());
for (ProjectInvitationVO projectInvitation : projectInvitations) {
projectInvitation.setForAccountId(newUser.getAccountId());
projectInvitation.setForUserId(newUser.getId());
update(projectInvitation.getId(), projectInvitation);
}
}

private SearchCriteria<ProjectInvitationVO> prepareAllFieldsSearchCriteria(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = AllFieldsSearch.create();

sc.setParametersIfNotNull("userId", userId);
sc.setParametersIfNotNull("accountId", accountId);
if (projectId != null && projectId != -1) {
sc.setParameters("projectId", projectId);
}

return sc;
}

@Override
public boolean isActive(long id, long timeout) {
SearchCriteria<ProjectInvitationVO> sc = InactiveSearch.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
--;
-- Schema upgrade cleanup from 4.22.1.0 to 4.23.0.0
--;

-- Delete stale project association entries for users that were removed
DELETE FROM `cloud`.`project_account` WHERE `user_id` IN (SELECT `id` FROM `cloud`.`user` WHERE `removed`);
DELETE FROM `cloud`.`project_invitations` WHERE `user_id` IN (SELECT `id` FROM `cloud`.`user` WHERE `removed`);
3 changes: 3 additions & 0 deletions server/src/main/java/com/cloud/projects/ProjectManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;

import com.cloud.user.Account;
import com.cloud.user.User;
import org.apache.cloudstack.framework.config.ConfigKey;

public interface ProjectManager extends ProjectService {
Expand Down Expand Up @@ -47,6 +48,8 @@ public interface ProjectManager extends ProjectService {

long getInvitationTimeout();

boolean cleanupProjectsForUser(Project project, User user);

public static final String MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT = "Message.CreateTungstenProject.Event";
public static final String MESSAGE_DELETE_TUNGSTEN_PROJECT_EVENT = "Message.DeleteTungstenProject.Event";

Expand Down
90 changes: 56 additions & 34 deletions server/src/main/java/com/cloud/projects/ProjectManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,37 @@ public boolean addUserToProject(Long projectId, String username, String email, L
}
}

/**
* Transfers all project associations and project invitations from one user to another.
*
* @param oldUser the user whose project associations are being transferred
* @param newUser the user to whom the project associations are being transferred
* @throws ResourceAllocationException if there is an issue with allocating the required project resources to the new user
*/
@Override
public void moveProjectAssociationsToUser(User oldUser, User newUser) throws ResourceAllocationException {
_projectInvitationDao.move(oldUser, newUser);

List<ProjectAccountVO> projectAccounts = _projectAccountDao.listBy(null, oldUser.getAccountId(), oldUser.getId());
if (projectAccounts.isEmpty()) {
return;
}

Account oldAccount = _accountDao.findById(oldUser.getAccountId());
Account newAccount = _accountDao.findById(newUser.getAccountId());
long requiredProjectsAmount = oldAccount.getId() != newAccount.getId()
? projectAccounts.stream().filter(pa -> pa.getAccountRole() == ProjectAccount.Role.Admin).count()
: 0L;

try (CheckedReservation projectReservation = new CheckedReservation(newAccount, ResourceType.project, null, null, requiredProjectsAmount, reservationDao, _resourceLimitMgr)) {
_projectAccountDao.move(oldUser, newUser);
if (requiredProjectsAmount > 0) {
_resourceLimitMgr.incrementResourceCount(newAccount.getId(), ResourceType.project, requiredProjectsAmount);
_resourceLimitMgr.decrementResourceCount(oldAccount.getId(), ResourceType.project, requiredProjectsAmount);
}
}
}

@Override
public Project findByNameAndDomainId(String name, long domainId) {
return _projectDao.findByNameAndDomain(name, domainId);
Expand Down Expand Up @@ -1033,50 +1064,41 @@ public boolean deleteUserFromProject(long projectId, long userId) {
//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);
boolean success = cleanupProjectsForUser(project, user);
if (!success) {
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);
return true;
}

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);
} else {
logger.info("Failed to delete project invite for user: {}", user);
}
}
}
/**
* Cleans up project associations and invitations for a specified user in a given project.
*
* @param project the project from which the user is being cleaned up; if null, cleanup applies to all projects associated with the user
* @param user the user whose project associations and invitations are being cleaned up
* @return true if any project accounts associated with the user were removed, false otherwise
*/
@Override
public boolean cleanupProjectsForUser(Project project, User user) {
return Transaction.execute((TransactionCallback<Boolean>) status -> {
Long projectId = project != null ? project.getId() : null;
long userId = user.getId();
long accountId = user.getAccountId();

@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());
_projectInvitationDao.removeBy(projectId, accountId, userId);

List<ProjectAccountVO> projectAccounts = _projectAccountDao.listBy(projectId, accountId, userId);
for (ProjectAccountVO projectAccount : projectAccounts) {
_projectAccountDao.remove(projectAccount.getId());
if (projectAccount.getAccountRole() == Role.Admin) {
_resourceLimitMgr.decrementResourceCount(user.getAccountId(), ResourceType.project);
_resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project);
}
if (success) {
logger.debug("Removed user {} from project. Removing any invite sent to the user", user);
ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId);
if (invite != null) {
success = success && _projectInvitationDao.remove(invite.getId());
}
}
return success;
logger.debug("Removed user [{}] from project [{}].", user, projectAccount.getProjectId());
}

return !projectAccounts.isEmpty();
});
}

Expand Down
5 changes: 3 additions & 2 deletions server/src/main/java/com/cloud/user/AccountManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;

import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
Expand Down Expand Up @@ -148,7 +149,7 @@ void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> s
* moves a user to another account within the same domain
* @return true if the user was successfully moved
*/
boolean moveUser(MoveUserCmd moveUserCmd);
boolean moveUser(MoveUserCmd moveUserCmd) throws ResourceAllocationException;

@Override
UserAccount updateUser(UpdateUserCmd cmd);
Expand Down Expand Up @@ -190,7 +191,7 @@ void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> s
ConfigKey<Boolean> UseSecretKeyInResponse = new ConfigKey<Boolean>("Advanced", Boolean.class, "use.secret.key.in.response", "false",
"This parameter allows the users to enable or disable of showing secret key as a part of response for various APIs. By default it is set to false.", true);

boolean moveUser(long id, Long domainId, Account newAccount);
boolean moveUser(long id, Long domainId, Account newAccount) throws ResourceAllocationException;

UserTwoFactorAuthenticator getUserTwoFactorAuthenticator(final Long domainId, final Long userAccountId);

Expand Down
Loading
Loading