mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch '4.18' into 4.19
This commit is contained in:
commit
d541e9014d
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -35,6 +35,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
tests: [ "smoke/test_accounts
|
tests: [ "smoke/test_accounts
|
||||||
|
smoke/test_account_access
|
||||||
smoke/test_affinity_groups
|
smoke/test_affinity_groups
|
||||||
smoke/test_affinity_groups_projects
|
smoke/test_affinity_groups_projects
|
||||||
smoke/test_annotations
|
smoke/test_annotations
|
||||||
|
|||||||
@ -208,7 +208,7 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) {
|
} 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) {
|
} else if (entity instanceof Network && accessType != null && accessType == AccessType.OperateEntry) {
|
||||||
_networkMgr.checkNetworkOperatePermissions(caller, (Network)entity);
|
_networkMgr.checkNetworkOperatePermissions(caller, (Network)entity);
|
||||||
} else if (entity instanceof VirtualRouter) {
|
} else if (entity instanceof VirtualRouter) {
|
||||||
@ -216,30 +216,58 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
|
|||||||
} else if (entity instanceof AffinityGroup) {
|
} else if (entity instanceof AffinityGroup) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (_accountService.isNormalUser(caller.getId())) {
|
validateCallerHasAccessToEntityOwner(caller, entity, accessType);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkOperationPermitted(Account caller, ControlledEntity entity) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_logger.error(String.format("Caller ID: %d does not have permission to access %s", caller.getId(), entityLog));
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkOperationPermitted(Account caller, ControlledEntity entity) {
|
||||||
User user = CallContext.current().getCallingUser();
|
User user = CallContext.current().getCallingUser();
|
||||||
Project project = projectDao.findByProjectAccountId(entity.getAccountId());
|
Project project = projectDao.findByProjectAccountId(entity.getAccountId());
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
|
|||||||
@ -2744,7 +2744,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||||||
throw new InvalidParameterValueException("Unable to find user by id");
|
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.
|
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<String, String> keys = new HashMap<String, String>();
|
Map<String, String> keys = new HashMap<String, String>();
|
||||||
keys.put("apikey", user.getApiKey());
|
keys.put("apikey", user.getApiKey());
|
||||||
@ -2753,6 +2755,19 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||||||
return keys;
|
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
|
@Override
|
||||||
public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() {
|
public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() {
|
||||||
return userTwoFactorAuthenticationProviders;
|
return userTwoFactorAuthenticationProviders;
|
||||||
@ -2787,6 +2802,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
Account account = _accountDao.findById(user.getAccountId());
|
Account account = _accountDao.findById(user.getAccountId());
|
||||||
|
preventRootDomainAdminAccessToRootAdminKeys(user, account);
|
||||||
checkAccess(caller, null, true, account);
|
checkAccess(caller, null, true, account);
|
||||||
|
|
||||||
// don't allow updating system user
|
// don't allow updating system user
|
||||||
|
|||||||
166
server/src/test/java/com/cloud/acl/DomainCheckerTest.java
Normal file
166
server/src/test/java/com/cloud/acl/DomainCheckerTest.java
Normal file
@ -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<Account, ControlledEntity, AccountVO> 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<Account, ControlledEntity, AccountVO> 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<Account, ControlledEntity, AccountVO> 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<Account, ControlledEntity, AccountVO> 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<Account, ControlledEntity, AccountVO> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -16,6 +16,34 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.user;
|
package com.cloud.user;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
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;
|
||||||
|
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
|
||||||
|
import org.apache.cloudstack.auth.UserAuthenticator;
|
||||||
|
import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
|
||||||
|
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
|
||||||
|
import org.apache.cloudstack.context.CallContext;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
|
|
||||||
import com.cloud.acl.DomainChecker;
|
import com.cloud.acl.DomainChecker;
|
||||||
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
|
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
|
||||||
import com.cloud.domain.Domain;
|
import com.cloud.domain.Domain;
|
||||||
@ -241,6 +269,63 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
|
|||||||
accountManagerImpl.getKeys(_listkeyscmd);
|
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
|
@Test
|
||||||
public void updateUserTestTimeZoneAndEmailNull() {
|
public void updateUserTestTimeZoneAndEmailNull() {
|
||||||
prepareMockAndExecuteUpdateUserTest(0);
|
prepareMockAndExecuteUpdateUserTest(0);
|
||||||
|
|||||||
198
test/integration/smoke/test_account_access.py
Normal file
198
test/integration/smoke/test_account_access.py
Normal file
@ -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")
|
||||||
Loading…
x
Reference in New Issue
Block a user