From 073863249abf36b6879359889f5731984391fa41 Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Thu, 29 Nov 2012 16:09:47 -0800 Subject: [PATCH] Some ACL POC work Conflicts: server/src/com/cloud/api/ApiDispatcher.java --- api/src/com/cloud/api/ACL.java | 31 +++++ .../com/cloud/api/commands/DeployVMCmd.java | 9 ++ server/src/com/cloud/api/ApiDispatcher.java | 127 +++++++++++++++++- server/src/com/cloud/api/ApiServer.java | 11 +- 4 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 api/src/com/cloud/api/ACL.java diff --git a/api/src/com/cloud/api/ACL.java b/api/src/com/cloud/api/ACL.java new file mode 100644 index 00000000000..1f376e9c3a6 --- /dev/null +++ b/api/src/com/cloud/api/ACL.java @@ -0,0 +1,31 @@ +// 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.api; + +import static java.lang.annotation.ElementType.FIELD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD }) +public @interface ACL { + + + Class resourceType(); +} diff --git a/api/src/com/cloud/api/commands/DeployVMCmd.java b/api/src/com/cloud/api/commands/DeployVMCmd.java index f67ee8f6003..da9f3ea7431 100644 --- a/api/src/com/cloud/api/commands/DeployVMCmd.java +++ b/api/src/com/cloud/api/commands/DeployVMCmd.java @@ -26,6 +26,7 @@ import java.util.Map; import org.apache.log4j.Logger; +import com.cloud.api.ACL; import com.cloud.api.ApiConstants; import com.cloud.api.BaseAsyncCreateCmd; import com.cloud.api.BaseCmd; @@ -43,7 +44,10 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.security.SecurityGroup; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; import com.cloud.template.VirtualMachineTemplate; @@ -69,6 +73,7 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { @Parameter(name=ApiConstants.SERVICE_OFFERING_ID, type=CommandType.LONG, required=true, description="the ID of the service offering for the virtual machine") private Long serviceOfferingId; + @ACL(resourceType=VirtualMachineTemplate.class) @IdentityMapper(entityTableName="vm_template") @Parameter(name=ApiConstants.TEMPLATE_ID, type=CommandType.LONG, required=true, description="the ID of the template for the virtual machine") private Long templateId; @@ -88,6 +93,7 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { private Long domainId; //Network information + @ACL(resourceType=Network.class) @IdentityMapper(entityTableName="networks") @Parameter(name=ApiConstants.NETWORK_IDS, type=CommandType.LIST, collectionType=CommandType.LONG, description="list of network ids used by virtual machine. Can't be specified with ipToNetworkList parameter") private List networkIds; @@ -112,14 +118,17 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { @Parameter(name=ApiConstants.SSH_KEYPAIR, type=CommandType.STRING, description="name of the ssh key pair used to login to the virtual machine") private String sshKeyPairName; + //@ACL(resourceType=Host.class) @IdentityMapper(entityTableName="host") @Parameter(name=ApiConstants.HOST_ID, type=CommandType.LONG, description="destination Host ID to deploy the VM to - parameter available for root admin only") private Long hostId; + //@ACL(resourceType=SecurityGroup.class) @IdentityMapper(entityTableName="security_group") @Parameter(name=ApiConstants.SECURITY_GROUP_IDS, type=CommandType.LIST, collectionType=CommandType.LONG, description="comma separated list of security groups id that going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupnames parameter") private List securityGroupIdList; + //@ACL(resourceType=SecurityGroup.class) @Parameter(name=ApiConstants.SECURITY_GROUP_NAMES, type=CommandType.LIST, collectionType=CommandType.STRING, description="comma separated list of security groups names that going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupids parameter") private List securityGroupNameList; diff --git a/server/src/com/cloud/api/ApiDispatcher.java b/server/src/com/cloud/api/ApiDispatcher.java index dfe4a1fc789..471dfc4b641 100755 --- a/server/src/com/cloud/api/ApiDispatcher.java +++ b/server/src/com/cloud/api/ApiDispatcher.java @@ -22,6 +22,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -29,6 +30,7 @@ import java.util.regex.Matcher; import org.apache.log4j.Logger; +import com.cloud.acl.ControlledEntity; import com.cloud.api.BaseCmd.CommandType; import com.cloud.api.commands.ListEventsCmd; import com.cloud.async.AsyncCommandQueued; @@ -42,14 +44,19 @@ import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.IdentityProxy; +import com.cloud.network.dao.NetworkDao; import com.cloud.server.ManagementServer; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountService; import com.cloud.user.UserContext; import com.cloud.utils.DateUtil; import com.cloud.utils.IdentityProxy; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.component.PluggableService; +import com.cloud.utils.db.GenericDao; import com.cloud.utils.exception.CSExceptionErrorCode; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.uuididentity.dao.IdentityDao; @@ -64,7 +71,10 @@ public class ApiDispatcher { AsyncJobManager _asyncMgr; IdentityDao _identityDao; Long _createSnapshotQueueSizeLimit; + AccountManager _accountMgr; + + Map> _daoNameMap = new HashMap>(); // singleton class private static ApiDispatcher s_instance = new ApiDispatcher(); @@ -76,6 +86,7 @@ public class ApiDispatcher { _locator = ComponentLocator.getLocator(ManagementServer.Name); _asyncMgr = _locator.getManager(AsyncJobManager.class); _identityDao = _locator.getDao(IdentityDao.class); + ConfigurationDao configDao = _locator.getDao(ConfigurationDao.class); Map configs = configDao.getConfiguration(); String strSnapshotLimit = configs.get(Config.ConcurrentSnapshotsThresholdPerHost.key()); @@ -88,13 +99,30 @@ public class ApiDispatcher { _createSnapshotQueueSizeLimit = snapshotLimit; } } + _accountMgr = _locator.getManager(AccountManager.class); + + _daoNameMap.put("com.cloud.network.Network", NetworkDao.class); + _daoNameMap.put("com.cloud.template.VirtualMachineTemplate", VMTemplateDao.class); + + } public void dispatchCreateCmd(BaseAsyncCreateCmd cmd, Map params) { - setupParameters(cmd, params); + List entitiesToAccess = new ArrayList(); + setupParameters(cmd, params, entitiesToAccess); plugService(cmd); + if(!entitiesToAccess.isEmpty()){ + //owner + Account caller = UserContext.current().getCaller(); + Account owner = s_instance._accountMgr.getActiveAccountById(cmd.getEntityOwnerId()); + s_instance._accountMgr.checkAccess(caller, null, true, owner); + + for(ControlledEntity entity : entitiesToAccess) + s_instance._accountMgr.checkAccess(caller, null, true, entity); + } + try { UserContext ctx = UserContext.current(); ctx.setAccountId(cmd.getEntityOwnerId()); @@ -135,8 +163,19 @@ public class ApiDispatcher { } public void dispatch(BaseCmd cmd, Map params) { - setupParameters(cmd, params); + List entitiesToAccess = new ArrayList(); + setupParameters(cmd, params, entitiesToAccess); ApiDispatcher.plugService(cmd); + + if(!entitiesToAccess.isEmpty()){ + //owner + Account caller = UserContext.current().getCaller(); + Account owner = s_instance._accountMgr.getActiveAccountById(cmd.getEntityOwnerId()); + s_instance._accountMgr.checkAccess(caller, null, true, owner); + for(ControlledEntity entity : entitiesToAccess) + s_instance._accountMgr.checkAccess(caller, null, true, entity); + } + try { UserContext ctx = UserContext.current(); ctx.setAccountId(cmd.getEntityOwnerId()); @@ -299,8 +338,10 @@ public class ApiDispatcher { } } - public static void setupParameters(BaseCmd cmd, Map params) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void setupParameters(BaseCmd cmd, Map params, List entitiesToAccess) { Map unpackedParams = cmd.unpackParams(params); + if (cmd instanceof BaseListCmd) { Object pageSizeObj = unpackedParams.get(ApiConstants.PAGE_SIZE); @@ -368,12 +409,90 @@ public class ApiDispatcher { throw new ServerApiException(BaseCmd.PARAM_ERROR, "Unable to execute API command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value. " + invEx.getMessage()); } catch (CloudRuntimeException cloudEx) { // FIXME: Better error message? This only happens if the API command is not executable, which typically -// means + //means // there was // and IllegalAccessException setting one of the parameters. throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Internal error executing API command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8)); } + + + //check access on the resource this field points to + try { + ACL checkAccess = field.getAnnotation(ACL.class); + CommandType fieldType = parameterAnnotation.type(); + + + if(checkAccess != null){ + // Verify that caller can perform actions in behalf of vm owner + //acumulate all Controlled Entities together. + if(checkAccess.resourceType() != null){ + Class entity = checkAccess.resourceType(); + + if(ControlledEntity.class.isAssignableFrom(entity)){ + if (s_logger.isDebugEnabled()) { + s_logger.debug("entity name is:" + entity.getName()); + } + + if(s_instance._daoNameMap.containsKey(entity.getName())){ + Class daoClass = s_instance._daoNameMap.get(entity.getName()); + GenericDao daoClassInstance = s_instance._locator.getDao(daoClass); + + //Check if the parameter type is a single Id or list of id's/name's + switch (fieldType) { + case LIST: + CommandType listType = parameterAnnotation.collectionType(); + switch (listType) { + case LONG: + List listParam = new ArrayList(); + listParam = (List)field.get(cmd); + + for(Long entityId : listParam){ + ControlledEntity entityObj = (ControlledEntity)daoClassInstance.findById(entityId); + entitiesToAccess.add(entityObj); + } + break; + /*case STRING: + List listParam = new ArrayList(); + listParam = (List)field.get(cmd); + for(String entityName: listParam){ + ControlledEntity entityObj = (ControlledEntity)daoClassInstance(entityId); + entitiesToAccess.add(entityObj); + } + break; + */ + default: + break; + } + break; + case LONG: + Long entityId = (Long)field.get(cmd); + ControlledEntity entityObj = (ControlledEntity)daoClassInstance.findById(entityId); + entitiesToAccess.add(entityObj); + break; + default: + break; + } + + + } + + } + + } + + } + + } catch (IllegalArgumentException e) { + s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible."); + throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() + " is not accessible]"); + } catch (IllegalAccessException e) { + s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible."); + throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() + " is not accessible]"); + } + } + + //check access on the entities. } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index a5c9ea5b1ee..cfd3b2594d8 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -81,6 +81,7 @@ import org.apache.http.protocol.ResponseDate; import org.apache.http.protocol.ResponseServer; import org.apache.log4j.Logger; +import com.cloud.acl.ControlledEntity; import com.cloud.api.response.ApiResponseSerializer; import com.cloud.api.response.ExceptionResponse; import com.cloud.api.response.ListResponse; @@ -487,8 +488,16 @@ public class ApiServer implements HttpRequestHandler { objectEntityTable = createCmd.getEntityTable(); params.put("id", objectId.toString()); } else { - ApiDispatcher.setupParameters(cmdObj, params); + List entitiesToAccess = new ArrayList(); + ApiDispatcher.setupParameters(cmdObj, params, entitiesToAccess); ApiDispatcher.plugService(cmdObj); + + if(!entitiesToAccess.isEmpty()){ + Account owner = s_instance._accountMgr.getActiveAccountById(cmdObj.getEntityOwnerId()); + s_instance._accountMgr.checkAccess(caller, null, true, owner); + + s_instance._accountMgr.checkAccess(caller, null, true, (ControlledEntity[])entitiesToAccess.toArray()); + } } BaseAsyncCmd asyncCmd = (BaseAsyncCmd) cmdObj;