diff --git a/api/src/org/apache/cloudstack/acl/SecurityChecker.java b/api/src/org/apache/cloudstack/acl/SecurityChecker.java index 8ca34d05b03..0b29fb48fc0 100644 --- a/api/src/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/org/apache/cloudstack/acl/SecurityChecker.java @@ -100,6 +100,26 @@ public interface SecurityChecker extends Adapter { */ boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) throws PermissionDeniedException; + /** + * Checks if the account can access multiple objects. + * + * @param caller + * account to check against. + * @param entities + * objects that the account is trying to access. + * @param accessType + * TODO + * @param action + * name of the API + * @return true if access allowed. false if this adapter cannot provide + * permission. + * @throws PermissionDeniedException + * if this adapter is suppose to authenticate ownership and the + * check failed. + */ + boolean checkAccess(Account caller, AccessType accessType, String action, ControlledEntity... entities) + throws PermissionDeniedException; + /** * Checks if the user belongs to an account that can access the object. diff --git a/server/src/com/cloud/acl/DomainChecker.java b/server/src/com/cloud/acl/DomainChecker.java index ea129f7aefe..14976cc67f3 100755 --- a/server/src/com/cloud/acl/DomainChecker.java +++ b/server/src/com/cloud/acl/DomainChecker.java @@ -341,4 +341,17 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { } 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; + } } diff --git a/server/src/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java index e9bdd8b107b..3f1b47f5a2d 100644 --- a/server/src/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java @@ -37,8 +37,10 @@ import org.apache.log4j.Logger; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.InfrastructureEntity; +import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.BaseCmd; @@ -54,7 +56,12 @@ import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd; import org.apache.cloudstack.api.command.user.event.ListEventsCmd; import org.apache.cloudstack.context.CallContext; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.DataCenter; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.utils.DateUtil; @@ -71,6 +78,17 @@ public class ParamProcessWorker implements DispatchWorker { @Inject protected EntityManager _entityMgr; + List _secChecker; + + public List getSecChecker() { + return _secChecker; + } + + @Inject + public void setSecChecker(List secChecker) { + _secChecker = secChecker; + } + @Override public void handle(final DispatchTask task) { processParameters(task.getCmd(), task.getParams()); @@ -214,27 +232,96 @@ public class ParamProcessWorker implements DispatchWorker { private void doAccessChecks(final BaseCmd cmd, final Map entitiesToAccess) { - final Account caller = CallContext.current().getCallingAccount(); - final Account owner = _accountMgr.getActiveAccountById(cmd.getEntityOwnerId()); + Account caller = CallContext.current().getCallingAccount(); - if (cmd instanceof BaseAsyncCreateCmd) { - //check that caller can access the owner account. - _accountMgr.checkAccess(caller, null, true, owner); - } + APICommand commandAnnotation = cmd.getClass().getAnnotation(APICommand.class); + String apiName = commandAnnotation != null ? commandAnnotation.name() : null; if (!entitiesToAccess.isEmpty()) { - //check that caller can access the owner account. - _accountMgr.checkAccess(caller, null, true, owner); - for (final Object entity : entitiesToAccess.keySet()) { + List entitiesToOperate = new ArrayList(); + + for (Object entity : entitiesToAccess.keySet()) { if (entity instanceof ControlledEntity) { - _accountMgr.checkAccess(caller, entitiesToAccess.get(entity), true, (ControlledEntity)entity); + + if (AccessType.OperateEntry == entitiesToAccess.get(entity)) { + entitiesToOperate.add((ControlledEntity) entity); + } else { + _accountMgr.checkAccess(caller, entitiesToAccess.get(entity), false, apiName, + (ControlledEntity) entity); + } } else if (entity instanceof InfrastructureEntity) { - //FIXME: Move this code in adapter, remove code from Account manager + if (entity instanceof DataCenter) { + checkZoneAccess(caller, (DataCenter) entity); + } else if (entity instanceof ServiceOffering) { + checkServiceOfferingAccess(caller, (ServiceOffering) entity); + } else if (entity instanceof DiskOffering) { + checkDiskOfferingAccess(caller, (DiskOffering) entity); + } } } + + if (!entitiesToOperate.isEmpty()) { + _accountMgr.checkAccess(caller, AccessType.OperateEntry, false, apiName, + (ControlledEntity[]) entitiesToOperate.toArray()); + } + } } + private void checkDiskOfferingAccess(Account caller, DiskOffering dof) { + for (SecurityChecker checker : _secChecker) { + if (checker.checkAccess(caller, dof)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + caller + " to disk offering:" + dof.getId() + " by " + + checker.getName()); + } + return; + } else { + throw new PermissionDeniedException("Access denied to " + caller + " by " + checker.getName()); + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + caller + " has access to disk offering:" + + dof.getId()); + } + + private void checkServiceOfferingAccess(Account caller, ServiceOffering sof) { + for (SecurityChecker checker : _secChecker) { + if (checker.checkAccess(caller, sof)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + caller + " to service offering:" + sof.getId() + " by " + + checker.getName()); + } + return; + } else { + throw new PermissionDeniedException("Access denied to " + caller + " by " + checker.getName()); + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + caller + " has access to service offering:" + + sof.getId()); + } + + private void checkZoneAccess(Account caller, DataCenter zone) { + for (SecurityChecker checker : _secChecker) { + if (checker.checkAccess(caller, zone)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + caller + " to zone:" + zone.getId() + " by " + + checker.getName()); + } + return; + } else { + throw new PermissionDeniedException("Access denied to " + caller + " by " + checker.getName() + + " for zone " + zone.getId()); + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + caller + " has access to zone:" + + zone.getId()); + } @SuppressWarnings({"unchecked", "rawtypes"}) private void setFieldValue(final Field field, final BaseCmd cmdObj, final Object paramObj, final Parameter annotation) throws IllegalArgumentException, ParseException { diff --git a/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedAPIAccessChecker.java b/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedAPIAccessChecker.java index 1afe5e8772e..9964d48e378 100644 --- a/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedAPIAccessChecker.java +++ b/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedAPIAccessChecker.java @@ -216,7 +216,6 @@ public class RoleBasedAPIAccessChecker extends AdapterBase implements APIChecker } private void addDefaultAclPolicyPermission(String apiName, Class cmdClass, RoleType role) { - AccessType accessType = null; Class[] entityTypes = null; if (cmdClass != null) { diff --git a/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedEntityAccessChecker.java b/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedEntityAccessChecker.java index bcc483fc090..295a10681be 100644 --- a/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedEntityAccessChecker.java +++ b/services/iam/plugin/src/org/apache/cloudstack/iam/RoleBasedEntityAccessChecker.java @@ -27,6 +27,7 @@ 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; @@ -107,14 +108,22 @@ public class RoleBasedEntityAccessChecker extends DomainChecker implements Secur permissions = _iamSrv.listPolicyPermissionByActionAndEntity(policy.getId(), action, entityType); if (permissions.isEmpty()) { if (accessType != null) { - permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), - accessType.toString(), entityType)); + for (AccessType type : AccessType.values()) { + if (type.ordinal() >= accessType.ordinal()) { + permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), + type.toString(), entityType)); + } + } } } } else { if (accessType != null) { - permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), - accessType.toString(), entityType)); + for (AccessType type : AccessType.values()) { + if (type.ordinal() >= accessType.ordinal()) { + permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(), + type.toString(), entityType)); + } + } } } for (IAMPolicyPermission permission : permissions) { @@ -145,6 +154,48 @@ public class RoleBasedEntityAccessChecker extends DomainChecker implements Secur 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))){ @@ -181,15 +232,8 @@ public class RoleBasedEntityAccessChecker extends DomainChecker implements Secur private List getEffectivePolicies(Account caller, ControlledEntity entity) { - // Get the static Policies of the Caller List policies = _iamSrv.listIAMPolicies(caller.getId()); - // add any dynamic policies w.r.t the entity - if (caller.getId() == entity.getAccountId()) { - // The caller owns the entity - policies.add(_iamSrv.getResourceOwnerPolicy()); - } - List groups = _iamSrv.listIAMGroups(caller.getId()); for (IAMGroup group : groups) { // for each group find the grand parent groups.