| // 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 java.util.List; | |
| import javax.inject.Inject; | |
| import org.apache.cloudstack.acl.ControlledEntity; | |
| import org.apache.cloudstack.acl.ProjectRole; | |
| import org.apache.cloudstack.acl.ProjectRolePermission; | |
| import org.apache.cloudstack.acl.ProjectRoleService; | |
| import org.apache.cloudstack.acl.RolePermissionEntity; | |
| import org.apache.cloudstack.acl.SecurityChecker; | |
| import org.apache.cloudstack.affinity.AffinityGroup; | |
| import org.apache.cloudstack.context.CallContext; | |
| import org.apache.cloudstack.query.QueryService; | |
| import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; | |
| import org.springframework.stereotype.Component; | |
| import com.cloud.dc.DataCenter; | |
| import com.cloud.dc.DedicatedResourceVO; | |
| import com.cloud.dc.dao.DedicatedResourceDao; | |
| import com.cloud.domain.Domain; | |
| import com.cloud.domain.dao.DomainDao; | |
| import com.cloud.exception.PermissionDeniedException; | |
| import com.cloud.exception.UnavailableCommandException; | |
| import com.cloud.network.Network; | |
| import com.cloud.network.NetworkModel; | |
| import com.cloud.network.router.VirtualRouter; | |
| import com.cloud.network.vpc.VpcOffering; | |
| import com.cloud.network.vpc.dao.VpcOfferingDetailsDao; | |
| import com.cloud.offering.DiskOffering; | |
| import com.cloud.offering.NetworkOffering; | |
| import com.cloud.offering.ServiceOffering; | |
| import com.cloud.offerings.dao.NetworkOfferingDetailsDao; | |
| import com.cloud.projects.Project; | |
| import com.cloud.projects.ProjectAccount; | |
| import com.cloud.projects.ProjectManager; | |
| import com.cloud.projects.dao.ProjectAccountDao; | |
| import com.cloud.projects.dao.ProjectDao; | |
| import com.cloud.service.dao.ServiceOfferingDetailsDao; | |
| import com.cloud.storage.LaunchPermissionVO; | |
| import com.cloud.storage.dao.LaunchPermissionDao; | |
| import com.cloud.template.VirtualMachineTemplate; | |
| import com.cloud.user.Account; | |
| import com.cloud.user.AccountService; | |
| import com.cloud.user.User; | |
| import com.cloud.user.dao.AccountDao; | |
| import com.cloud.utils.component.AdapterBase; | |
| import com.cloud.utils.exception.CloudRuntimeException; | |
| @Component | |
| public class DomainChecker extends AdapterBase implements SecurityChecker { | |
| @Inject | |
| DomainDao _domainDao; | |
| @Inject | |
| AccountDao _accountDao; | |
| @Inject | |
| LaunchPermissionDao _launchPermissionDao; | |
| @Inject | |
| ProjectManager _projectMgr; | |
| @Inject | |
| ProjectAccountDao _projectAccountDao; | |
| @Inject | |
| NetworkModel _networkMgr; | |
| @Inject | |
| private DedicatedResourceDao _dedicatedDao; | |
| @Inject | |
| AccountService _accountService; | |
| @Inject | |
| DiskOfferingDetailsDao diskOfferingDetailsDao; | |
| @Inject | |
| ServiceOfferingDetailsDao serviceOfferingDetailsDao; | |
| @Inject | |
| NetworkOfferingDetailsDao networkOfferingDetailsDao; | |
| @Inject | |
| VpcOfferingDetailsDao vpcOfferingDetailsDao; | |
| @Inject | |
| private ProjectRoleService projectRoleService; | |
| @Inject | |
| private ProjectDao projectDao; | |
| @Inject | |
| private AccountService accountService; | |
| protected DomainChecker() { | |
| super(); | |
| } | |
| /** | |
| * | |
| * public template can be used by other accounts in: | |
| * | |
| * 1. the same domain | |
| * 2. in sub-domains | |
| * 3. domain admin of parent domains | |
| * | |
| * In addition to those, everyone can access the public templates in domains that set "share.public.templates.with.other.domains" config to true. | |
| * | |
| * @param template template object | |
| * @param owner owner of the template | |
| * @param caller who wants to access to the template | |
| */ | |
| private void checkPublicTemplateAccess(VirtualMachineTemplate template, Account owner, Account caller){ | |
| if (QueryService.SharePublicTemplatesWithOtherDomains.valueIn(owner.getDomainId()) || | |
| caller.getDomainId() == owner.getDomainId() || | |
| _domainDao.isChildDomain(owner.getDomainId(), caller.getDomainId())) { | |
| return; | |
| } | |
| if (caller.getType() == Account.Type.NORMAL || caller.getType() == Account.Type.PROJECT) { | |
| throw new PermissionDeniedException(caller + "is not allowed to access the template " + template); | |
| } else if (caller.getType() == Account.Type.DOMAIN_ADMIN || caller.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) { | |
| if (!_domainDao.isChildDomain(caller.getDomainId(), owner.getDomainId())) { | |
| throw new PermissionDeniedException(caller + "is not allowed to access the template " + template); | |
| } | |
| } | |
| } | |
| @Override | |
| public boolean checkAccess(Account caller, Domain domain) throws PermissionDeniedException { | |
| if (caller.getState() != Account.State.ENABLED) { | |
| throw new PermissionDeniedException(String.format("Account %s is disabled.", caller)); | |
| } | |
| if (domain == null) { | |
| throw new PermissionDeniedException(String.format("Provided domain is NULL, cannot check access for account [%s]", caller)); | |
| } | |
| long domainId = domain.getId(); | |
| if (_accountService.isNormalUser(caller.getId())) { | |
| if (caller.getDomainId() != domainId) { | |
| throw new PermissionDeniedException(String.format("Account %s does not have permission to operate within domain id=%s", caller, domain.getUuid())); | |
| } | |
| } else if (!_domainDao.isChildDomain(caller.getDomainId(), domainId)) { | |
| throw new PermissionDeniedException(String.format("Account %s does not have permission to operate within domain id=%s", caller, domain.getUuid())); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean checkAccess(User user, Domain domain) throws PermissionDeniedException { | |
| if (user.getRemoved() != null) { | |
| throw new PermissionDeniedException(user + " is no longer active."); | |
| } | |
| Account account = _accountDao.findById(user.getAccountId()); | |
| return checkAccess(account, domain); | |
| } | |
| @Override | |
| public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType) | |
| throws PermissionDeniedException { | |
| if (entity instanceof VirtualMachineTemplate) { | |
| VirtualMachineTemplate template = (VirtualMachineTemplate)entity; | |
| Account owner = _accountDao.findById(template.getAccountId()); | |
| // validate that the template is usable by the account | |
| if (!template.isPublicTemplate()) { | |
| if (_accountService.isRootAdmin(caller.getId()) || (owner.getId() == caller.getId())) { | |
| return true; | |
| } | |
| //special handling for the project case | |
| if (owner.getType() == Account.Type.PROJECT && _projectMgr.canAccessProjectAccount(caller, owner.getId())) { | |
| return true; | |
| } | |
| // since the current account is not the owner of the template, check the launch permissions table to see if the | |
| // account can launch a VM from this template | |
| LaunchPermissionVO permission = _launchPermissionDao.findByTemplateAndAccount(template.getId(), caller.getId()); | |
| if (permission == null) { | |
| throw new PermissionDeniedException(String.format("Account %s does not have permission to launch instances from template %s", caller, template)); | |
| } | |
| } else { | |
| // Domain admin and regular user can delete/modify only templates created by them | |
| if (accessType != null && accessType == AccessType.OperateEntry) { | |
| if (!_accountService.isRootAdmin(caller.getId()) && owner.getId() != caller.getId()) { | |
| // For projects check if the caller account can access the project account | |
| if (owner.getType() != Account.Type.PROJECT || !(_projectMgr.canAccessProjectAccount(caller, owner.getId()))) { | |
| throw new PermissionDeniedException("Domain Admin and regular users can modify only their own Public templates"); | |
| } | |
| } | |
| } else if (caller.getType() != Account.Type.ADMIN) { | |
| checkPublicTemplateAccess(template, owner, caller); | |
| } | |
| } | |
| return true; | |
| } else if (entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) { | |
| _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) { | |
| _networkMgr.checkRouterPermissions(caller, (VirtualRouter)entity); | |
| } else if (entity instanceof AffinityGroup) { | |
| return false; | |
| } else { | |
| 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."); | |
| if (_accountService.isRootAdmin(caller.getId())) { | |
| return; | |
| } | |
| if (caller.getId() == entity.getAccountId()) { | |
| return; | |
| } | |
| Account owner = _accountDao.findById(entity.getAccountId()); | |
| String entityLog = String.format("entity [owner: %s, type: %s]", owner, entity.getEntityType().getSimpleName()); | |
| if (owner == null) { | |
| 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())) { | |
| logger.error("Caller: {} does not have permission to modify project with " + | |
| "owner: {}", caller, owner); | |
| throw exception; | |
| } | |
| } else if (!_projectMgr.canAccessProjectAccount(caller, owner.getId())) { | |
| logger.error("Caller: {} does not have permission to access project with " + | |
| "owner: {}", caller, owner); | |
| throw exception; | |
| } | |
| checkOperationPermitted(caller, entity); | |
| return; | |
| } | |
| logger.error("Caller: {} does not have permission to access {}", caller, entityLog); | |
| throw exception; | |
| } | |
| protected boolean checkOperationPermitted(Account caller, ControlledEntity entity) { | |
| User user = CallContext.current().getCallingUser(); | |
| Project project = projectDao.findByProjectAccountId(entity.getAccountId()); | |
| if (project == null) { | |
| throw new CloudRuntimeException("Unable to find project to which the entity belongs to"); | |
| } | |
| ProjectAccount projectUser = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); | |
| String apiCommandName = CallContext.current().getApiName(); | |
| if (accountService.isRootAdmin(caller.getId()) || accountService.isDomainAdmin(caller.getAccountId())) { | |
| return true; | |
| } | |
| if (projectUser != null) { | |
| if (projectUser.getAccountRole() == ProjectAccount.Role.Admin) { | |
| return true; | |
| } else { | |
| return isPermitted(project, projectUser, apiCommandName); | |
| } | |
| } | |
| ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(project.getId(), caller.getAccountId()); | |
| if (projectAccount != null) { | |
| if (projectAccount.getAccountRole() == ProjectAccount.Role.Admin) { | |
| return true; | |
| } else { | |
| return isPermitted(project, projectAccount, apiCommandName); | |
| } | |
| } | |
| throw new UnavailableCommandException("The given command '" + apiCommandName + "' either does not exist or is not available for the user"); | |
| } | |
| private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { | |
| ProjectRole projectRole = null; | |
| if(projectUser.getProjectRoleId() != null) { | |
| projectRole = projectRoleService.findProjectRole(projectUser.getProjectRoleId(), project.getId()); | |
| } | |
| if (projectRole == null) { | |
| return true; | |
| } | |
| for (ProjectRolePermission permission : projectRoleService.findAllProjectRolePermissions(project.getId(), projectRole.getId())) { | |
| if (permission.getRule().matches(apiCommandName)) { | |
| if (RolePermissionEntity.Permission.ALLOW.equals(permission.getPermission())) { | |
| return true; | |
| } else { | |
| throw new PermissionDeniedException("The given command '" + apiCommandName + "' either does not exist or is not available for the user"); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { | |
| Account account = _accountDao.findById(user.getAccountId()); | |
| return checkAccess(account, entity, null); | |
| } | |
| @Override | |
| public boolean checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException { | |
| boolean hasAccess = false; | |
| // Check for domains | |
| if (account == null || dof == null) { | |
| hasAccess = true; | |
| } else { | |
| //admin has all permissions | |
| if (_accountService.isRootAdmin(account.getId())) { | |
| hasAccess = true; | |
| } | |
| //if account is normal user or domain admin | |
| //check if account's domain is a child of offering's domain (Note: This is made consistent with the list command for disk offering) | |
| else if (_accountService.isNormalUser(account.getId()) | |
| || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN | |
| || _accountService.isDomainAdmin(account.getId()) | |
| || account.getType() == Account.Type.PROJECT) { | |
| final List<Long> doDomainIds = diskOfferingDetailsDao.findDomainIds(dof.getId()); | |
| if (doDomainIds.isEmpty()) { | |
| hasAccess = true; | |
| } else { | |
| for (Long domainId : doDomainIds) { | |
| if (_domainDao.isChildDomain(domainId, account.getDomainId())) { | |
| hasAccess = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Check for zones | |
| if (hasAccess && dof != null && zone != null) { | |
| final List<Long> doZoneIds = diskOfferingDetailsDao.findZoneIds(dof.getId()); | |
| hasAccess = doZoneIds.isEmpty() || doZoneIds.contains(zone.getId()); | |
| } | |
| return hasAccess; | |
| } | |
| @Override | |
| public boolean checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { | |
| boolean hasAccess = false; | |
| // Check for domains | |
| if (account == null || so == null) { | |
| hasAccess = true; | |
| } else { | |
| //admin has all permissions | |
| if (_accountService.isRootAdmin(account.getId())) { | |
| hasAccess = true; | |
| } | |
| //if account is normal user or domain admin | |
| //check if account's domain is a child of offering's domain (Note: This is made consistent with the list command for service offering) | |
| else if (_accountService.isNormalUser(account.getId()) | |
| || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN | |
| || _accountService.isDomainAdmin(account.getId()) | |
| || account.getType() == Account.Type.PROJECT) { | |
| final List<Long> soDomainIds = serviceOfferingDetailsDao.findDomainIds(so.getId()); | |
| if (soDomainIds.isEmpty()) { | |
| hasAccess = true; | |
| } else { | |
| for (Long domainId : soDomainIds) { | |
| if (_domainDao.isChildDomain(domainId, account.getDomainId())) { | |
| hasAccess = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Check for zones | |
| if (hasAccess && so != null && zone != null) { | |
| final List<Long> soZoneIds = serviceOfferingDetailsDao.findZoneIds(so.getId()); | |
| hasAccess = soZoneIds.isEmpty() || soZoneIds.contains(zone.getId()); | |
| } | |
| return hasAccess; | |
| } | |
| @Override | |
| public boolean checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException { | |
| boolean hasAccess = false; | |
| // Check for domains | |
| if (account == null || nof == null) { | |
| hasAccess = true; | |
| } else { | |
| //admin has all permissions | |
| if (_accountService.isRootAdmin(account.getId())) { | |
| hasAccess = true; | |
| } | |
| //if account is normal user or domain admin | |
| //check if account's domain is a child of offering's domain (Note: This is made consistent with the list command for disk offering) | |
| else if (_accountService.isNormalUser(account.getId()) | |
| || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN | |
| || _accountService.isDomainAdmin(account.getId()) | |
| || account.getType() == Account.Type.PROJECT) { | |
| final List<Long> noDomainIds = networkOfferingDetailsDao.findDomainIds(nof.getId()); | |
| if (noDomainIds.isEmpty()) { | |
| hasAccess = true; | |
| } else { | |
| for (Long domainId : noDomainIds) { | |
| if (_domainDao.isChildDomain(domainId, account.getDomainId())) { | |
| hasAccess = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Check for zones | |
| if (hasAccess && nof != null && zone != null) { | |
| final List<Long> doZoneIds = networkOfferingDetailsDao.findZoneIds(nof.getId()); | |
| hasAccess = doZoneIds.isEmpty() || doZoneIds.contains(zone.getId()); | |
| } | |
| return hasAccess; | |
| } | |
| @Override | |
| public boolean checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException { | |
| boolean hasAccess = false; | |
| // Check for domains | |
| if (account == null || vof == null) { | |
| hasAccess = true; | |
| } else { | |
| //admin has all permissions | |
| if (_accountService.isRootAdmin(account.getId())) { | |
| hasAccess = true; | |
| } | |
| //if account is normal user or domain admin | |
| //check if account's domain is a child of offering's domain (Note: This is made consistent with the list command for disk offering) | |
| else if (_accountService.isNormalUser(account.getId()) | |
| || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN | |
| || _accountService.isDomainAdmin(account.getId()) | |
| || account.getType() == Account.Type.PROJECT) { | |
| final List<Long> voDomainIds = vpcOfferingDetailsDao.findDomainIds(vof.getId()); | |
| if (voDomainIds.isEmpty()) { | |
| hasAccess = true; | |
| } else { | |
| for (Long domainId : voDomainIds) { | |
| if (_domainDao.isChildDomain(domainId, account.getDomainId())) { | |
| hasAccess = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Check for zones | |
| if (hasAccess && vof != null && zone != null) { | |
| final List<Long> doZoneIds = vpcOfferingDetailsDao.findZoneIds(vof.getId()); | |
| hasAccess = doZoneIds.isEmpty() || doZoneIds.contains(zone.getId()); | |
| } | |
| return hasAccess; | |
| } | |
| @Override | |
| public boolean checkAccess(Account account, DataCenter zone) throws PermissionDeniedException { | |
| if (account == null || zone.getDomainId() == null) {//public zone | |
| return true; | |
| } else { | |
| //admin has all permissions | |
| if (_accountService.isRootAdmin(account.getId())) { | |
| return true; | |
| } | |
| //if account is normal user | |
| //check if account's domain is a child of zone's domain | |
| else if (_accountService.isNormalUser(account.getId()) || account.getType() == Account.Type.PROJECT) { | |
| // if zone is dedicated to an account check that the accountId | |
| // matches. | |
| DedicatedResourceVO dedicatedZone = _dedicatedDao.findByZoneId(zone.getId()); | |
| if (dedicatedZone != null) { | |
| if (dedicatedZone.getAccountId() != null) { | |
| if (dedicatedZone.getAccountId() == account.getId()) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| } | |
| if (account.getDomainId() == zone.getDomainId()) { | |
| return true; //zone and account at exact node | |
| } else { | |
| Domain domainRecord = _domainDao.findById(account.getDomainId()); | |
| if (domainRecord != null) { | |
| while (true) { | |
| if (domainRecord.getId() == zone.getDomainId()) { | |
| //found as a child | |
| return true; | |
| } | |
| if (domainRecord.getParent() != null) { | |
| domainRecord = _domainDao.findById(domainRecord.getParent()); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| //not found | |
| return false; | |
| } | |
| //if account is domain admin | |
| //check if the account's domain is either child of zone's domain, or if zone's domain is child of account's domain | |
| else if (_accountService.isDomainAdmin(account.getId())) { | |
| if (account.getDomainId() == zone.getDomainId()) { | |
| return true; //zone and account at exact node | |
| } else { | |
| Domain zoneDomainRecord = _domainDao.findById(zone.getDomainId()); | |
| Domain accountDomainRecord = _domainDao.findById(account.getDomainId()); | |
| if (accountDomainRecord != null) { | |
| Domain localRecord = accountDomainRecord; | |
| while (true) { | |
| if (localRecord.getId() == zone.getDomainId()) { | |
| //found as a child | |
| return true; | |
| } | |
| if (localRecord.getParent() != null) { | |
| localRecord = _domainDao.findById(localRecord.getParent()); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| //didn't find in upper tree | |
| if (zoneDomainRecord != null && | |
| accountDomainRecord != null && | |
| zoneDomainRecord.getPath().contains(accountDomainRecord.getPath())) { | |
| return true; | |
| } | |
| } | |
| //not found | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| @Override | |
| public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) | |
| throws PermissionDeniedException { | |
| if (action != null && ("SystemCapability".equals(action))) { | |
| if (caller != null && caller.getType() == Account.Type.ADMIN) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } else if (action != null && ("DomainCapability".equals(action))) { | |
| if (caller != null && caller.getType() == Account.Type.DOMAIN_ADMIN) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } else if (action != null && ("DomainResourceCapability".equals(action))) { | |
| if (caller != null && caller.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| return checkAccess(caller, entity, accessType); | |
| } | |
| @Override | |
| public boolean checkAccess(Account caller, AccessType accessType, String action, ControlledEntity... entities) | |
| throws PermissionDeniedException { | |
| // returns true only if access to all entities is granted | |
| for (ControlledEntity entity : entities) { | |
| if (!checkAccess(caller, entity, accessType, action)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } |