diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index d4c23e8d62be..2e0884cbce24 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2112,14 +2112,30 @@ public boolean deleteUserAccount(long accountId) { protected void checkIfAccountManagesProjects(long accountId) { List managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); - if (!CollectionUtils.isEmpty(managedProjectIds)) { - throw new InvalidParameterValueException(String.format( + + if (CollectionUtils.isEmpty(managedProjectIds)) { + return; + } + + List activeManagedProjects = new ArrayList<>(); + + for (Long projectId : managedProjectIds) { + ProjectVO project = _projectDao.findById(projectId); + if (project != null && project.getRemoved() == null) { + activeManagedProjects.add(projectId); + } + } + + if (!activeManagedProjects.isEmpty()) { + throw new InvalidParameterValueException( + String.format( "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", - accountId, managedProjectIds + accountId, activeManagedProjects )); } } + protected boolean isDeleteNeeded(AccountVO account, long accountId, Account caller) { if (account == null) { logger.info(String.format("The account, identified by id %d, doesn't exist", accountId )); diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 2aeb43469d19..a2684bfe8d60 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -26,6 +26,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Date; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.Role; @@ -54,8 +56,11 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.InjectMocks; +import org.mockito.Spy; import org.springframework.beans.factory.NoSuchBeanDefinitionException; + import com.cloud.acl.DomainChecker; import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd; import com.cloud.domain.Domain; @@ -75,10 +80,17 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.projects.ProjectVO; +import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.projects.dao.ProjectDao; @RunWith(MockitoJUnitRunner.class) public class AccountManagerImplTest extends AccountManagetImplTestBase { + @Spy + @InjectMocks + private AccountManagerImpl accountManagerImpl; + @Mock private UserVmManagerImpl _vmMgr; @Mock @@ -117,9 +129,16 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase { @Mock private ProjectAccountVO projectAccountVO; + @Mock private Project project; + @Mock + private ProjectAccountDao _projectAccountDao; + + @Mock + private ProjectDao _projectDao; + @Mock PasswordPolicyImpl passwordPolicyMock; @@ -1589,4 +1608,23 @@ public void testcheckCallerApiPermissionsForUserOperationsNotAllowedApis() { accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock); } + + @Test + public void testCheckIfAccountManagesOnlyDeletedProjectsDoesNotThrow() { + long accountId = 42L; + long projectId = 100L; + + Mockito.when(_projectAccountDao.listAdministratedProjectIds(accountId)) + .thenReturn(List.of(projectId)); + + ProjectVO deletedProject = Mockito.mock(ProjectVO.class); + Mockito.when(deletedProject.getRemoved()).thenReturn(new Date()); + + Mockito.when(_projectDao.findById(projectId)) + .thenReturn(deletedProject); + + accountManagerImpl.checkIfAccountManagesProjects(accountId); + + } + }