Hi David, I am putting together the ideas currently and will provide a link to a document soon describing them.
Thanks, Prachi -----Original Message----- From: David Nalley [mailto:da...@gnsa.us] Sent: Saturday, November 10, 2012 3:22 AM To: cloudstack-dev@incubator.apache.org Subject: Re: git commit: Some ACL POC work Hi Prachi: I am still trying to get caught up on my backlog of mail, is there a place where you've discussed some of these ACL ideas? --David On Wed, Oct 24, 2012 at 5:11 PM, <prachida...@apache.org> wrote: > Updated Branches: > refs/heads/acl f16b5103d -> 3058520ab > > > Some ACL POC work > > > Project: > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo > Commit: > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/305 > 8520a > Tree: > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/30585 > 20a > Diff: > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/30585 > 20a > > Branch: refs/heads/acl > Commit: 3058520ab3f09c31c142b41e37912e5ddd8d074a > Parents: f16b510 > Author: Prachi Damle <pra...@cloud.com> > Authored: Thu Oct 18 13:32:45 2012 -0700 > Committer: Prachi Damle <pra...@cloud.com> > Committed: Thu Oct 18 13:32:45 2012 -0700 > > ---------------------------------------------------------------------- > api/src/com/cloud/api/ACL.java | 31 +++++ > api/src/com/cloud/api/commands/DeployVMCmd.java | 9 ++ > server/src/com/cloud/api/ApiDispatcher.java | 126 +++++++++++++++++- > server/src/com/cloud/api/ApiServer.java | 11 ++- > 4 files changed, 172 insertions(+), 5 deletions(-) > ---------------------------------------------------------------------- > > > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/30585 > 20a/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 > 0000000..1f376e9 > --- /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(); > +} > > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/30585 > 20a/api/src/com/cloud/api/commands/DeployVMCmd.java > ---------------------------------------------------------------------- > diff --git a/api/src/com/cloud/api/commands/DeployVMCmd.java > b/api/src/com/cloud/api/commands/DeployVMCmd.java > index f67ee8f..da9f3ea 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<Long> 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<Long> 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<String> securityGroupNameList; > > > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/30585 > 20a/server/src/com/cloud/api/ApiDispatcher.java > ---------------------------------------------------------------------- > diff --git a/server/src/com/cloud/api/ApiDispatcher.java > b/server/src/com/cloud/api/ApiDispatcher.java > index 8eade00..de7bdc6 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; > @@ -40,12 +42,17 @@ 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.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; > @@ -59,7 +66,10 @@ public class ApiDispatcher { > ComponentLocator _locator; > AsyncJobManager _asyncMgr; > IdentityDao _identityDao; > + AccountManager _accountMgr; > > + > + Map<String, Class<? extends GenericDao>> _daoNameMap = new > + HashMap<String, Class<? extends GenericDao>>(); > // singleton class > private static ApiDispatcher s_instance = new ApiDispatcher(); > > @@ -71,13 +81,30 @@ public class ApiDispatcher { > _locator = ComponentLocator.getLocator(ManagementServer.Name); > _asyncMgr = _locator.getManager(AsyncJobManager.class); > _identityDao = _locator.getDao(IdentityDao.class); > + _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<String, > String> params) { > > - setupParameters(cmd, params); > + List<ControlledEntity> entitiesToAccess = new > ArrayList<ControlledEntity>(); > + 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()); > @@ -118,8 +145,19 @@ public class ApiDispatcher { > } > > public void dispatch(BaseCmd cmd, Map<String, String> params) { > - setupParameters(cmd, params); > + List<ControlledEntity> entitiesToAccess = new > ArrayList<ControlledEntity>(); > + 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()); > @@ -270,8 +308,10 @@ public class ApiDispatcher { > } > } > > - public static void setupParameters(BaseCmd cmd, Map<String, String> > params) { > + @SuppressWarnings({ "unchecked", "rawtypes" }) > + public static void setupParameters(BaseCmd cmd, Map<String, > + String> params, List<ControlledEntity> entitiesToAccess) { > Map<String, Object> unpackedParams = > cmd.unpackParams(params); > + > > if (cmd instanceof BaseListCmd) { > Object pageSizeObj = > unpackedParams.get(ApiConstants.PAGE_SIZE); > @@ -339,12 +379,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<? extends GenericDao> 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<Long> listParam > = new ArrayList<Long>(); > + > + listParam = (List)field.get(cmd); > + > + for(Long entityId : > listParam){ > + > ControlledEntity entityObj = > (ControlledEntity)daoClassInstance.findById(entityId); > + > entitiesToAccess.add(entityObj); > + } > + break; > + /*case STRING: > + List<String> > listParam = new ArrayList<String>(); > + 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" }) > > http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/30585 > 20a/server/src/com/cloud/api/ApiServer.java > ---------------------------------------------------------------------- > diff --git a/server/src/com/cloud/api/ApiServer.java > b/server/src/com/cloud/api/ApiServer.java > index eb5e770..3186d95 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; > @@ -486,8 +487,16 @@ public class ApiServer implements HttpRequestHandler { > objectEntityTable = createCmd.getEntityTable(); > params.put("id", objectId.toString()); > } else { > - ApiDispatcher.setupParameters(cmdObj, params); > + List<ControlledEntity> entitiesToAccess = new > ArrayList<ControlledEntity>(); > + 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; >