| // 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 org.apache.cloudstack.iam; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.log4j.Logger; |
| |
| import org.apache.cloudstack.acl.ControlledEntity; |
| import org.apache.cloudstack.acl.PermissionScope; |
| import org.apache.cloudstack.acl.SecurityChecker; |
| import org.apache.cloudstack.acl.SecurityChecker.AccessType; |
| import org.apache.cloudstack.api.InternalIdentity; |
| import org.apache.cloudstack.iam.api.IAMGroup; |
| import org.apache.cloudstack.iam.api.IAMPolicy; |
| import org.apache.cloudstack.iam.api.IAMPolicyPermission; |
| import org.apache.cloudstack.iam.api.IAMService; |
| |
| import com.cloud.acl.DomainChecker; |
| import com.cloud.domain.dao.DomainDao; |
| import com.cloud.exception.InvalidParameterValueException; |
| import com.cloud.exception.PermissionDeniedException; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountService; |
| |
| public class RoleBasedEntityAccessChecker extends DomainChecker implements SecurityChecker { |
| |
| private static final Logger s_logger = Logger.getLogger(RoleBasedEntityAccessChecker.class.getName()); |
| |
| @Inject |
| AccountService _accountService; |
| |
| @Inject DomainDao _domainDao; |
| |
| @Inject |
| IAMService _iamSrv; |
| |
| |
| @Override |
| public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType) |
| throws PermissionDeniedException { |
| return checkAccess(caller, entity, accessType, null); |
| } |
| |
| private String buildAccessCacheKey(Account caller, ControlledEntity entity, AccessType accessType, String action) { |
| StringBuffer key = new StringBuffer(); |
| key.append(caller.getAccountId()); |
| key.append("-"); |
| String entityType = null; |
| if (entity != null && entity.getEntityType() != null) { |
| entityType = entity.getEntityType().getSimpleName(); |
| if (entity instanceof InternalIdentity) { |
| entityType += ((InternalIdentity)entity).getId(); |
| } |
| } |
| key.append(entityType != null ? entityType : "null"); |
| key.append("-"); |
| key.append(accessType != null ? accessType.toString() : "null"); |
| key.append("-"); |
| key.append(action != null ? action : "null"); |
| return key.toString(); |
| } |
| |
| @Override |
| public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) |
| throws PermissionDeniedException { |
| |
| if (caller == null) { |
| throw new InvalidParameterValueException("Caller cannot be passed as NULL to IAM!"); |
| } |
| |
| if (entity == null && action == null) { |
| throw new InvalidParameterValueException("Entity and action cannot be both NULL in checkAccess!"); |
| } |
| |
| // check IAM cache first |
| String accessKey = buildAccessCacheKey(caller, entity, accessType, action); |
| CheckAccessResult allowDeny = (CheckAccessResult)_iamSrv.getFromIAMCache(accessKey); |
| if (allowDeny != null) { |
| s_logger.debug("IAM access check for " + accessKey + " from cache: " + allowDeny.isAllow()); |
| if (allowDeny.isAllow()) { |
| return true; |
| } else { |
| if (allowDeny.getDenyMsg() != null) { |
| throw new PermissionDeniedException(allowDeny.getDenyMsg()); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| if (entity == null && action != null) { |
| // check if caller can do this action |
| List<IAMPolicy> policies = _iamSrv.listIAMPolicies(caller.getAccountId()); |
| |
| boolean isAllowed = _iamSrv.isActionAllowedForPolicies(action, policies); |
| if (!isAllowed) { |
| String msg = "The action '" + action + "' not allowed for account " + caller; |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(msg)); |
| throw new PermissionDeniedException(msg); |
| } |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(true)); |
| return true; |
| } |
| |
| |
| // if a Project entity, skip |
| Account entityAccount = _accountService.getAccount(entity.getAccountId()); |
| if (entityAccount != null && entityAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) { |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(false)); |
| return false; |
| } |
| |
| String entityType = null; |
| if (entity.getEntityType() != null) { |
| entityType = entity.getEntityType().getSimpleName(); |
| } |
| |
| if (accessType == null) { |
| accessType = AccessType.UseEntry; |
| } |
| |
| // get all Policies of this caller by considering recursive domain group policy |
| List<IAMPolicy> policies = getEffectivePolicies(caller); |
| HashMap<IAMPolicy, Boolean> policyPermissionMap = new HashMap<IAMPolicy, Boolean>(); |
| |
| for (IAMPolicy policy : policies) { |
| List<IAMPolicyPermission> permissions = new ArrayList<IAMPolicyPermission>(); |
| |
| if (action != null) { |
| permissions = _iamSrv.listPolicyPermissionByActionAndEntity(policy.getId(), action, entityType); |
| if (permissions.isEmpty()) { |
| if (accessType != null) { |
| for (AccessType type : AccessType.values()) { |
| if (type.ordinal() >= accessType.ordinal()) { |
| permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), |
| type.toString(), entityType)); |
| } |
| } |
| } |
| } |
| } else { |
| if (accessType != null) { |
| for (AccessType type : AccessType.values()) { |
| if (type.ordinal() >= accessType.ordinal()) { |
| permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), |
| type.toString(), entityType)); |
| } |
| } |
| } |
| } |
| for (IAMPolicyPermission permission : permissions) { |
| if (checkPermissionScope(caller, permission.getScope(), permission.getScopeId(), entity)) { |
| if (permission.getEntityType().equals(entityType)) { |
| policyPermissionMap.put(policy, permission.getPermission().isGranted()); |
| break; |
| } else if (permission.getEntityType().equals("*")) { |
| policyPermissionMap.put(policy, permission.getPermission().isGranted()); |
| } |
| } |
| } |
| if (policyPermissionMap.containsKey(policy) && policyPermissionMap.get(policy)) { |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(true)); |
| return true; |
| } |
| } |
| |
| if (!policies.isEmpty()) { // Since we reach this point, none of the |
| // roles granted access |
| |
| String msg = "Account " + caller + " does not have permission to access resource " + entity |
| + " for access type: " + accessType; |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug(msg); |
| } |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(msg)); |
| throw new PermissionDeniedException(msg); |
| } |
| |
| _iamSrv.addToIAMCache(accessKey, new CheckAccessResult(false)); |
| return false; |
| } |
| |
| @Override |
| public boolean checkAccess(Account caller, AccessType accessType, String action, ControlledEntity... entities) |
| throws PermissionDeniedException { |
| |
| // operate access on multiple entities? |
| if (accessType != null && accessType == AccessType.OperateEntry) { |
| // In this case caller MUST own n-1 entities. |
| |
| for (ControlledEntity entity : entities) { |
| checkAccess(caller, entity, accessType, action); |
| |
| boolean otherEntitiesAccess = true; |
| |
| for (ControlledEntity otherEntity : entities) { |
| if (otherEntity.getAccountId() == caller.getAccountId() |
| || (checkAccess(caller, otherEntity, accessType, action) && otherEntity.getAccountId() == entity |
| .getAccountId())) { |
| continue; |
| } else { |
| otherEntitiesAccess = false; |
| break; |
| } |
| } |
| |
| if (otherEntitiesAccess) { |
| return true; |
| } |
| } |
| |
| throw new PermissionDeniedException(caller |
| + " does not have permission to perform this operation on these resources"); |
| |
| } else { |
| for (ControlledEntity entity : entities) { |
| if (!checkAccess(caller, entity, accessType, action)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| private boolean checkPermissionScope(Account caller, String scope, Long scopeId, ControlledEntity entity) { |
| |
| if(scopeId != null && !scopeId.equals(new Long(IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER))){ |
| //scopeId is set |
| if (scope.equals(PermissionScope.ACCOUNT.name())) { |
| if(scopeId == entity.getAccountId()){ |
| return true; |
| } |
| } else if (scope.equals(PermissionScope.DOMAIN.name())) { |
| if (_domainDao.isChildDomain(scopeId, entity.getDomainId())) { |
| return true; |
| } |
| } else if (scope.equals(PermissionScope.RESOURCE.name())) { |
| if (entity instanceof InternalIdentity) { |
| InternalIdentity entityWithId = (InternalIdentity) entity; |
| if(scopeId.equals(entityWithId.getId())){ |
| return true; |
| } |
| } |
| } |
| } else if (scopeId == null || scopeId.equals(new Long(IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER))) { |
| if (scope.equals(PermissionScope.ACCOUNT.name())) { |
| if(caller.getAccountId() == entity.getAccountId()){ |
| return true; |
| } |
| } else if (scope.equals(PermissionScope.DOMAIN.name())) { |
| if (_domainDao.isChildDomain(caller.getDomainId(), entity.getDomainId())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private List<IAMPolicy> getEffectivePolicies(Account caller) { |
| |
| List<IAMPolicy> policies = _iamSrv.listIAMPolicies(caller.getId()); |
| |
| List<IAMGroup> groups = _iamSrv.listIAMGroups(caller.getId()); |
| for (IAMGroup group : groups) { |
| // for each group find the grand parent groups. |
| List<IAMGroup> parentGroups = _iamSrv.listParentIAMGroups(group.getId()); |
| for (IAMGroup parentGroup : parentGroups) { |
| policies.addAll(_iamSrv.listRecursiveIAMPoliciesByGroup(parentGroup.getId())); |
| } |
| } |
| |
| return policies; |
| } |
| |
| private class CheckAccessResult { |
| boolean allow; |
| String denyMsg; |
| |
| public CheckAccessResult(boolean aw) { |
| this(aw, null); |
| } |
| |
| public CheckAccessResult(String msg) { |
| this(false, msg); |
| } |
| |
| public CheckAccessResult(boolean aw, String msg) { |
| allow = aw; |
| denyMsg = msg; |
| } |
| |
| public boolean isAllow() { |
| return allow; |
| } |
| |
| public void setAllow(boolean aw) { |
| allow = aw; |
| } |
| |
| |
| public String getDenyMsg() { |
| return denyMsg; |
| } |
| |
| public void setDenyMsg(String denyMsg) { |
| this.denyMsg = denyMsg; |
| } |
| |
| } |
| } |