diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 087e2575f73f..8c07af29b8ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,7 @@ jobs: fail-fast: false matrix: tests: [ "smoke/test_accounts + smoke/test_account_access smoke/test_affinity_groups smoke/test_affinity_groups_projects smoke/test_annotations diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index a8c9ab84f7ec..7930be49ec13 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -208,7 +208,7 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a return true; } else if (entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) { - _networkMgr.checkNetworkPermissions(caller, (Network)entity); + _networkMgr.checkNetworkPermissions(caller, (Network) entity); } else if (entity instanceof Network && accessType != null && accessType == AccessType.OperateEntry) { _networkMgr.checkNetworkOperatePermissions(caller, (Network)entity); } else if (entity instanceof VirtualRouter) { @@ -216,30 +216,58 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a } else if (entity instanceof AffinityGroup) { return false; } else { - if (_accountService.isNormalUser(caller.getId())) { - Account account = _accountDao.findById(entity.getAccountId()); - String errorMessage = String.format("%s does not have permission to operate with resource", caller); - if (account != null && account.getType() == Account.Type.PROJECT) { - //only project owner can delete/modify the project - if (accessType != null && accessType == AccessType.ModifyProject) { - if (!_projectMgr.canModifyProjectAccount(caller, account.getId())) { - throw new PermissionDeniedException(errorMessage); - } - } else if (!_projectMgr.canAccessProjectAccount(caller, account.getId())) { - throw new PermissionDeniedException(errorMessage); - } - checkOperationPermitted(caller, entity); - } else { - if (caller.getId() != entity.getAccountId()) { - throw new PermissionDeniedException(errorMessage); - } + validateCallerHasAccessToEntityOwner(caller, entity, accessType); + } + return true; + } + + protected void validateCallerHasAccessToEntityOwner(Account caller, ControlledEntity entity, AccessType accessType) { + PermissionDeniedException exception = new PermissionDeniedException("Caller does not have permission to operate with provided resource."); + String entityLog = String.format("entity [owner ID: %d, type: %s]", entity.getAccountId(), + entity.getEntityType().getSimpleName()); + + if (_accountService.isRootAdmin(caller.getId())) { + return; + } + + if (caller.getId() == entity.getAccountId()) { + return; + } + + Account owner = _accountDao.findById(entity.getAccountId()); + if (owner == null) { + s_logger.error(String.format("Owner not found for %s", entityLog)); + throw exception; + } + + Account.Type callerAccountType = caller.getType(); + if ((callerAccountType == Account.Type.DOMAIN_ADMIN || callerAccountType == Account.Type.RESOURCE_DOMAIN_ADMIN) && + _domainDao.isChildDomain(caller.getDomainId(), owner.getDomainId())) { + return; + } + + if (owner.getType() == Account.Type.PROJECT) { + // only project owner can delete/modify the project + if (accessType == AccessType.ModifyProject) { + if (!_projectMgr.canModifyProjectAccount(caller, owner.getId())) { + s_logger.error(String.format("Caller ID: %d does not have permission to modify project with " + + "owner ID: %d", caller.getId(), owner.getId())); + throw exception; } + } else if (!_projectMgr.canAccessProjectAccount(caller, owner.getId())) { + s_logger.error(String.format("Caller ID: %d does not have permission to access project with " + + "owner ID: %d", caller.getId(), owner.getId())); + throw exception; } + checkOperationPermitted(caller, entity); + return; } - return true; + + s_logger.error(String.format("Caller ID: %d does not have permission to access %s", caller.getId(), entityLog)); + throw exception; } - private boolean checkOperationPermitted(Account caller, ControlledEntity entity) { + protected boolean checkOperationPermitted(Account caller, ControlledEntity entity) { User user = CallContext.current().getCallingUser(); Project project = projectDao.findByProjectAccountId(entity.getAccountId()); if (project == null) { diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 3684657faec1..5c7aec5a1b7f 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2717,7 +2717,9 @@ public Map getKeys(Long userId) { 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); + User caller = CallContext.current().getCallingUser(); + preventRootDomainAdminAccessToRootAdminKeys(caller, account); + checkAccess(caller, account); Map keys = new HashMap(); keys.put("apikey", user.getApiKey()); @@ -2726,6 +2728,19 @@ public Map getKeys(Long userId) { return keys; } + protected void preventRootDomainAdminAccessToRootAdminKeys(User caller, ControlledEntity account) { + if (isDomainAdminForRootDomain(caller) && isRootAdmin(account.getAccountId())) { + String msg = String.format("Caller Username %s does not have access to root admin keys", caller.getUsername()); + s_logger.error(msg); + throw new PermissionDeniedException(msg); + } + } + + protected boolean isDomainAdminForRootDomain(User callingUser) { + AccountVO caller = _accountDao.findById(callingUser.getAccountId()); + return caller.getType() == Account.Type.DOMAIN_ADMIN && caller.getDomainId() == Domain.ROOT_DOMAIN; + } + @Override public List listUserTwoFactorAuthenticationProviders() { return userTwoFactorAuthenticationProviders; @@ -2760,6 +2775,7 @@ public String[] createApiKeyAndSecretKey(RegisterCmd cmd) { } Account account = _accountDao.findById(user.getAccountId()); + preventRootDomainAdminAccessToRootAdminKeys(user, account); checkAccess(caller, null, true, account); // don't allow updating system user diff --git a/server/src/test/java/com/cloud/acl/DomainCheckerTest.java b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java new file mode 100644 index 000000000000..a5ec41306d85 --- /dev/null +++ b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java @@ -0,0 +1,166 @@ +// 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.acl; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.SecurityChecker; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.ProjectManager; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Ternary; + +@RunWith(MockitoJUnitRunner.class) +public class DomainCheckerTest { + + @Mock + AccountService _accountService; + @Mock + AccountDao _accountDao; + @Mock + DomainDao _domainDao; + @Mock + ProjectManager _projectMgr; + + @Spy + @InjectMocks + DomainChecker domainChecker; + + private ControlledEntity getMockedEntity(long accountId) { + ControlledEntity entity = Mockito.mock(Account.class); + Mockito.when(entity.getAccountId()).thenReturn(accountId); + Mockito.when(entity.getEntityType()).thenReturn((Class)Account.class); + return entity; + } + + @Test + public void testRootAdminHasAccess() { + Account rootAdmin = Mockito.mock(Account.class); + Mockito.when(rootAdmin.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(2L); + Mockito.when(_accountService.isRootAdmin(rootAdmin.getId())).thenReturn(true); + + domainChecker.validateCallerHasAccessToEntityOwner(rootAdmin, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testCallerIsOwner() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(1L); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test(expected = PermissionDeniedException.class) + public void testOwnerNotFound() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(2L); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(null); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testDomainAdminHasAccess() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + Mockito.when(caller.getDomainId()).thenReturn(100L); + Mockito.when(caller.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + ControlledEntity entity = getMockedEntity(2L); + AccountVO owner = Mockito.mock(AccountVO.class); + Mockito.when(owner.getDomainId()).thenReturn(101L); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(owner); + Mockito.when(_domainDao.isChildDomain(100L, 101L)).thenReturn(true); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + private Ternary getProjectAccessCheckResources() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(100L); + Mockito.when(caller.getType()).thenReturn(Account.Type.PROJECT); + ControlledEntity entity = getMockedEntity(2L); + AccountVO projectAccount = Mockito.mock(AccountVO.class); + Mockito.when(projectAccount.getId()).thenReturn(2L); + Mockito.when(projectAccount.getType()).thenReturn(Account.Type.PROJECT); + return new Ternary<>(caller, entity, projectAccount); + } + + @Test + public void testProjectOwnerCanModify() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canModifyProjectAccount(caller, projectAccount.getId())).thenReturn(true); + Mockito.doReturn(true).when(domainChecker).checkOperationPermitted(caller, entity); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test(expected = PermissionDeniedException.class) + public void testProjectOwnerCannotModify() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canModifyProjectAccount(caller, projectAccount.getId())).thenReturn(false); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testProjectOwnerCanAccess() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canAccessProjectAccount(caller, projectAccount.getId())).thenReturn(true); + Mockito.doReturn(true).when(domainChecker).checkOperationPermitted(caller, entity); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ListEntry); + } + + @Test(expected = PermissionDeniedException.class) + public void testProjectOwnerCannotAccess() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canAccessProjectAccount(caller, projectAccount.getId())).thenReturn(false); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ListEntry); + } + +} diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 2f3a68e20af6..262fdeced3e1 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.HashMap; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; @@ -240,6 +241,63 @@ public void testgetUserCmd() { accountManagerImpl.getKeys(_listkeyscmd); } + @Test(expected = PermissionDeniedException.class) + public void testGetUserKeysCmdDomainAdminRootAdminUser() { + CallContext.register(callingUser, callingAccount); + Mockito.when(_listkeyscmd.getID()).thenReturn(2L); + Mockito.when(accountManagerImpl.getActiveUser(2L)).thenReturn(userVoMock); + Mockito.when(userAccountDaoMock.findById(2L)).thenReturn(userAccountVO); + Mockito.when(userAccountVO.getAccountId()).thenReturn(2L); + Mockito.when(userDetailsDaoMock.listDetailsKeyPairs(Mockito.anyLong())).thenReturn(null); + + // Queried account - admin account + AccountVO adminAccountMock = Mockito.mock(AccountVO.class); + Mockito.when(adminAccountMock.getAccountId()).thenReturn(2L); + Mockito.when(_accountDao.findByIdIncludingRemoved(2L)).thenReturn(adminAccountMock); + Mockito.lenient().when(accountService.isRootAdmin(2L)).thenReturn(true); + Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), + Mockito.nullable(ControlledEntity.class), Mockito.nullable(AccessType.class), Mockito.anyString())).thenReturn(true); + + // Calling account is domain admin of the ROOT domain + Mockito.lenient().when(callingAccount.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + Mockito.lenient().when(callingAccount.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + + Mockito.lenient().when(callingUser.getAccountId()).thenReturn(2L); + Mockito.lenient().when(_accountDao.findById(2L)).thenReturn(callingAccount); + + Mockito.lenient().when(accountService.isDomainAdmin(Mockito.anyLong())).thenReturn(Boolean.TRUE); + Mockito.lenient().when(accountMock.getAccountId()).thenReturn(2L); + + accountManagerImpl.getKeys(_listkeyscmd); + } + + @Test + public void testPreventRootDomainAdminAccessToRootAdminKeysNormalUser() { + User user = Mockito.mock(User.class); + ControlledEntity entity = Mockito.mock(ControlledEntity.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + AccountVO account = Mockito.mock(AccountVO.class); + Mockito.when(account.getType()).thenReturn(Account.Type.NORMAL); + Mockito.when(_accountDao.findById(1L)).thenReturn(account); + accountManagerImpl.preventRootDomainAdminAccessToRootAdminKeys(user, entity); + Mockito.verify(accountManagerImpl, Mockito.never()).isRootAdmin(Mockito.anyLong()); + } + + @Test(expected = PermissionDeniedException.class) + public void testPreventRootDomainAdminAccessToRootAdminKeysRootDomainAdminUser() { + User user = Mockito.mock(User.class); + ControlledEntity entity = Mockito.mock(ControlledEntity.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + AccountVO account = Mockito.mock(AccountVO.class); + Mockito.when(account.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + Mockito.when(account.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + Mockito.when(_accountDao.findById(1L)).thenReturn(account); + Mockito.when(entity.getAccountId()).thenReturn(1L); + Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), + Mockito.nullable(ControlledEntity.class), Mockito.nullable(AccessType.class), Mockito.anyString())).thenReturn(true); + accountManagerImpl.preventRootDomainAdminAccessToRootAdminKeys(user, entity); + } + @Test public void updateUserTestTimeZoneAndEmailNull() { prepareMockAndExecuteUpdateUserTest(0); diff --git a/test/integration/smoke/test_account_access.py b/test/integration/smoke/test_account_access.py new file mode 100644 index 000000000000..97eeced63860 --- /dev/null +++ b/test/integration/smoke/test_account_access.py @@ -0,0 +1,198 @@ +# 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. +""" BVT tests for Account User Access +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import * +from marvin.lib.base import (Account, + User, + Domain) +from marvin.lib.common import (get_domain) +from marvin.cloudstackAPI import (getUserKeys) +from marvin.cloudstackException import CloudstackAPIException +from nose.plugins.attrib import attr + +_multiprocess_shared_ = True + +class TestAccountAccess(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestAccountAccess, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + + cls.domains = [] + cls.domain_admins = {} + cls.domain_users = {} + cls.account_users = {} + + domain_data = { + "name": "domain_1" + } + cls.domain_1 = Domain.create( + cls.apiclient, + domain_data, + ) + cls._cleanup.append(cls.domain_1) + cls.domains.append(cls.domain_1) + domain_data["name"] = "domain_11" + cls.domain_11 = Domain.create( + cls.apiclient, + domain_data, + parentdomainid=cls.domain_1.id + ) + cls._cleanup.append(cls.domain_11) + cls.domains.append(cls.domain_11) + domain_data["name"] = "domain_12" + cls.domain_12 = Domain.create( + cls.apiclient, + domain_data, + parentdomainid=cls.domain_1.id + ) + cls._cleanup.append(cls.domain_12) + cls.domains.append(cls.domain_12) + domain_data["name"] = "domain_2" + cls.domain_2 = Domain.create( + cls.apiclient, + domain_data, + ) + cls._cleanup.append(cls.domain_2) + cls.domains.append(cls.domain_2) + + + for d in cls.domains: + cls.create_domainadmin_and_user(d) + + @classmethod + def tearDownClass(cls): + super(TestAccountAccess, cls).tearDownClass() + + @classmethod + def create_account(cls, domain, is_admin): + cls.debug(f"Creating account for domain {domain.name}, admin: {is_admin}") + data = { + "email": "admin-" + domain.name + "@test.com", + "firstname": "Admin", + "lastname": domain.name, + "username": "admin-" + domain.name, + "password": "password" + } + if is_admin == False: + data["email"] = "user-" + domain.name + "@test.com" + data["firstname"] = "User" + data["username"] = "user-" + domain.name + account = Account.create( + cls.apiclient, + data, + admin=is_admin, + domainid=domain.id + ) + cls._cleanup.append(account) + if is_admin == True: + cls.domain_admins[domain.id] = account + else: + cls.domain_users[domain.id] = account + + user = User.create( + cls.apiclient, + data, + account=account.name, + domainid=account.domainid) + cls._cleanup.append(user) + cls.account_users[account.id] = user + + @classmethod + def create_domainadmin_and_user(cls, domain): + cls.debug(f"Creating accounts for domain #{domain.id} {domain.name}") + cls.create_account(domain, True) + cls.create_account(domain, False) + + def get_user_keys(self, api_client, user_id): + getUserKeysCmd = getUserKeys.getUserKeysCmd() + getUserKeysCmd.id = user_id + return api_client.getUserKeys(getUserKeysCmd) + + def is_child_domain(self, parent_domain, child_domain): + if not parent_domain or not child_domain: + return False + parent_domain_prefix = parent_domain.split('-')[0] + child_domain_prefix = child_domain.split('-')[0] + if not parent_domain_prefix or not child_domain_prefix: + return False + return child_domain_prefix.startswith(parent_domain_prefix) + + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") + def test_01_user_access(self): + """ + Test user account is not accessing any other account + """ + + domain_user_accounts = [value for value in self.domain_users.values()] + all_account_users = [value for value in self.account_users.values()] + for user_account in domain_user_accounts: + current_account_user = self.account_users[user_account.id] + self.debug(f"Check for account {user_account.name} with user {current_account_user.username}") + user_api_client = self.testClient.getUserApiClient( + UserName=user_account.name, + DomainName=user_account.domain + ) + for user in all_account_users: + self.debug(f"Checking access for user {user.username} associated with account {user.account}") + try: + self.get_user_keys(user_api_client, user.id) + self.debug(f"API successful") + if user.id != current_account_user.id: + self.fail(f"User account #{user_account.id} was able to access another account #{user.id}") + except CloudstackAPIException as e: + self.debug(f"Exception occurred: {e}") + if user.id == current_account_user.id: + self.fail(f"User account #{user_account.id} not able to access own account") + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") + def test_02_domain_admin_access(self): + """ + Test domain admin account is not accessing any other account from unauthorized domain + """ + + domain_admin_accounts = [value for value in self.domain_admins.values()] + all_account_users = [value for value in self.account_users.values()] + for admin_account in domain_admin_accounts: + current_account_user = self.account_users[admin_account.id] + self.debug(f"Check for domain admin {admin_account.name} with user {current_account_user.username}, {current_account_user.domain}") + admin_api_client = self.testClient.getUserApiClient( + UserName=admin_account.name, + DomainName=admin_account.domain + ) + for user in all_account_users: + self.debug(f"Checking access for user {user.username}, {user.domain} associated with account {user.account}") + try: + self.get_user_keys(admin_api_client, user.id) + self.debug(f"API successful") + if self.is_child_domain(current_account_user.domain, user.domain) == False: + self.fail(f"User account #{admin_account.id} was able to access another account #{user.id}") + except CloudstackAPIException as e: + self.debug(f"Exception occurred: {e}") + if self.is_child_domain(current_account_user.domain, user.domain) == True: + self.fail(f"User account #{admin_account.id} not able to access own account")