diff --git a/api/src/org/apache/cloudstack/acl/AclPermission.java b/api/src/org/apache/cloudstack/acl/AclPolicyPermission.java similarity index 92% rename from api/src/org/apache/cloudstack/acl/AclPermission.java rename to api/src/org/apache/cloudstack/acl/AclPolicyPermission.java index aff15034a84..02d557e1b93 100644 --- a/api/src/org/apache/cloudstack/acl/AclPermission.java +++ b/api/src/org/apache/cloudstack/acl/AclPolicyPermission.java @@ -19,10 +19,12 @@ package org.apache.cloudstack.acl; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.InternalIdentity; -public interface AclPermission extends InternalIdentity { +public interface AclPolicyPermission extends InternalIdentity { String getAction(); + long getAclPolicyId(); + String getEntityType(); AccessType getAccessType(); diff --git a/api/src/org/apache/cloudstack/api/response/AclPermissionResponse.java b/api/src/org/apache/cloudstack/api/response/AclPermissionResponse.java index 9329b249036..ad928dc4e40 100644 --- a/api/src/org/apache/cloudstack/api/response/AclPermissionResponse.java +++ b/api/src/org/apache/cloudstack/api/response/AclPermissionResponse.java @@ -19,7 +19,7 @@ package org.apache.cloudstack.api.response; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.acl.AclEntityType; -import org.apache.cloudstack.acl.AclPermission; +import org.apache.cloudstack.acl.AclPolicyPermission; import org.apache.cloudstack.acl.PermissionScope; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -46,7 +46,7 @@ public class AclPermissionResponse extends BaseResponse { @SerializedName(ApiConstants.ACL_ALLOW_DENY) @Param(description = "allow or deny of this permission") - private AclPermission.Permission permission; + private AclPolicyPermission.Permission permission; public AclEntityType getEntityType() { return entityType; @@ -80,11 +80,11 @@ public class AclPermissionResponse extends BaseResponse { this.scopeId = scopeId; } - public AclPermission.Permission getPermission() { + public AclPolicyPermission.Permission getPermission() { return permission; } - public void setPermission(AclPermission.Permission permission) { + public void setPermission(AclPolicyPermission.Permission permission) { this.permission = permission; } diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 24da2306384..0d2dedbdb5d 100644 --- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -325,7 +325,8 @@ - + + diff --git a/engine/schema/src/org/apache/cloudstack/acl/AclPermissionVO.java b/engine/schema/src/org/apache/cloudstack/acl/AclPolicyPermissionVO.java similarity index 88% rename from engine/schema/src/org/apache/cloudstack/acl/AclPermissionVO.java rename to engine/schema/src/org/apache/cloudstack/acl/AclPolicyPermissionVO.java index ef8cfc48d47..0b23b0bc944 100644 --- a/engine/schema/src/org/apache/cloudstack/acl/AclPermissionVO.java +++ b/engine/schema/src/org/apache/cloudstack/acl/AclPolicyPermissionVO.java @@ -32,14 +32,17 @@ import org.apache.cloudstack.acl.SecurityChecker.AccessType; import com.cloud.utils.db.GenericDao; @Entity -@Table(name = ("acl_permission")) -public class AclPermissionVO implements AclPermission { +@Table(name = ("acl_policy_permission")) +public class AclPolicyPermissionVO implements AclPolicyPermission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private long id; + @Column(name = "policy_id") + private long aclPolicyId; + @Column(name = "action") private String action; @@ -67,12 +70,14 @@ public class AclPermissionVO implements AclPermission { @Column(name = GenericDao.CREATED_COLUMN) private Date created; - public AclPermissionVO() { + public AclPolicyPermissionVO() { } - public AclPermissionVO(String action, String entityType, AccessType accessType, PermissionScope scope, + public AclPolicyPermissionVO(long aclPolicyId, String action, String entityType, AccessType accessType, + PermissionScope scope, Long scopeId, Permission permission) { + this.aclPolicyId = aclPolicyId; this.action = action; this.entityType = entityType; this.accessType = accessType; @@ -86,6 +91,11 @@ public class AclPermissionVO implements AclPermission { return id; } + @Override + public long getAclPolicyId() { + return aclPolicyId; + } + @Override public String getEntityType() { diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDao.java b/engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDao.java similarity index 85% rename from engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDao.java rename to engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDao.java index d24b0445166..f8d3cb5c220 100644 --- a/engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDao.java +++ b/engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDao.java @@ -16,9 +16,11 @@ // under the License. package org.apache.cloudstack.acl.dao; -import org.apache.cloudstack.acl.AclPermissionVO; + +import org.apache.cloudstack.acl.AclPolicyPermissionVO; + import com.cloud.utils.db.GenericDao; -public interface AclPermissionDao extends GenericDao { +public interface AclPolicyPermissionDao extends GenericDao { } diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDaoImpl.java b/engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDaoImpl.java similarity index 83% rename from engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDaoImpl.java rename to engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDaoImpl.java index 1b5f63ecb86..1160d30c959 100644 --- a/engine/schema/src/org/apache/cloudstack/acl/dao/AclPermissionDaoImpl.java +++ b/engine/schema/src/org/apache/cloudstack/acl/dao/AclPolicyPermissionDaoImpl.java @@ -20,12 +20,14 @@ import java.util.Map; import javax.naming.ConfigurationException; -import org.apache.cloudstack.acl.AclPermissionVO; +import org.apache.cloudstack.acl.AclPolicyPermissionVO; + import com.cloud.utils.db.GenericDaoBase; -public class AclPermissionDaoImpl extends GenericDaoBase implements AclPermissionDao { +public class AclPolicyPermissionDaoImpl extends GenericDaoBase implements + AclPolicyPermissionDao { - public AclPermissionDaoImpl() + public AclPolicyPermissionDaoImpl() { } diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index 1dfbd4511a1..5b0d7ccb6b5 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -52,14 +52,13 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.cloudstack.acl.APIChecker; -import org.apache.cloudstack.acl.AclPermissionVO; -import org.apache.cloudstack.acl.AclPolicyPermissionMapVO; +import org.apache.cloudstack.acl.AclPolicyPermissionVO; import org.apache.cloudstack.acl.PermissionScope; import org.apache.cloudstack.acl.RoleType; -import org.apache.cloudstack.acl.AclPermission.Permission; +import org.apache.cloudstack.acl.AclPolicyPermission.Permission; import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.cloudstack.acl.dao.AclPermissionDao; -import org.apache.cloudstack.acl.dao.AclPolicyPermissionMapDao; +import org.apache.cloudstack.acl.dao.AclPolicyPermissionDao; +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -149,12 +148,14 @@ import com.cloud.user.UserAccount; import com.cloud.user.UserVO; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.PropertiesUtil; import com.cloud.utils.StringUtils; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; @@ -180,9 +181,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer List _pluggableServices; List _apiAccessCheckers; @Inject - private AclPermissionDao _aclPermissionDao; - @Inject - private AclPolicyPermissionMapDao _aclPolicyPermissionMapDao; + private AclPolicyPermissionDao _aclPermissionDao; @Inject protected ApiAsyncJobDispatcher _asyncDispatcher; @@ -190,6 +189,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer private static final DateFormat _dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); private static Map>> _apiNameCmdClassMap = new HashMap>>(); + private static Set commandsPropertiesOverrides = new HashSet(); + private static Map> commandsPropertiesRoleBasedApisMap = new HashMap>(); + + private static ExecutorService _executor = new ThreadPoolExecutor(10, 150, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("ApiServer")); public ApiServer() { @@ -197,6 +200,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer @Override public boolean configure(String name, Map params) throws ConfigurationException { + processMapping(PropertiesUtil.processConfigFile(new String[] { "commands.properties" })); return true; } @@ -233,6 +237,40 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer } } + // drop all default policy api permissions - we reload them every time + // to include any chanegs done to the @APICommand or + // commands.properties. + SearchBuilder sb = _aclPermissionDao.createSearchBuilder(); + sb.and("policyId", sb.entity().getAclPolicyId(), SearchCriteria.Op.EQ); + sb.and("resourceType", sb.entity().getEntityType(), SearchCriteria.Op.NULL); + sb.and("scope", sb.entity().getScope(), SearchCriteria.Op.EQ); + sb.done(); + + SearchCriteria permissionSC = sb.create(); + + for (RoleType role : RoleType.values()) { + permissionSC.setParameters("policyId", role.ordinal() + 1); + switch (role) { + case User: + permissionSC.setParameters("scope", PermissionScope.ACCOUNT.toString()); + break; + + case Admin: + permissionSC.setParameters("scope", PermissionScope.ALL.toString()); + break; + + case DomainAdmin: + permissionSC.setParameters("scope", PermissionScope.DOMAIN.toString()); + break; + + case ResourceAdmin: + permissionSC.setParameters("scope", PermissionScope.DOMAIN.toString()); + break; + } + _aclPermissionDao.expunge(permissionSC); + + } + for(Class cmdClass: cmdClasses) { APICommand at = cmdClass.getAnnotation(APICommand.class); if (at == null) { @@ -246,52 +284,27 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer } apiCmdList.add(cmdClass); - boolean isReadCommand = false; - BaseCmd cmdObj; - try { - cmdObj = (BaseCmd) cmdClass.newInstance(); - if (cmdObj instanceof BaseListCmd) { - isReadCommand = true; - } - } catch (Exception e) { - } - - for (RoleType role : at.authorized()) { - AclPermissionVO apiPermission = null; - switch (role) { - case User: - apiPermission = new AclPermissionVO(apiName, null, null, PermissionScope.ACCOUNT, null, - Permission.Allow); - break; - - case Admin: - apiPermission = new AclPermissionVO(apiName, null, null, PermissionScope.ALL, null, - Permission.Allow); - break; - - case DomainAdmin: - apiPermission = new AclPermissionVO(apiName, null, null, PermissionScope.DOMAIN, null, - Permission.Allow); - break; - - case ResourceAdmin: - apiPermission = new AclPermissionVO(apiName, null, null, PermissionScope.DOMAIN, null, - Permission.Allow); - break; - } - - if (apiPermission != null) { - if (isReadCommand) { - apiPermission.setAccessType(AccessType.ListEntry); - } - _aclPermissionDao.persist(apiPermission); - AclPolicyPermissionMapVO policyPermMapEntry = new AclPolicyPermissionMapVO(role.ordinal() + 1, - apiPermission.getId()); - _aclPolicyPermissionMapDao.persist(policyPermMapEntry); + if (!commandsPropertiesOverrides.contains(apiName)) { + for (RoleType role : at.authorized()) { + addDefaultAclPolicyPermission(apiName, cmdClass, role); } } } + // read commands.properties and load api acl permissions - + // commands.properties overrides any @APICommand authorization + + for (String apiName : commandsPropertiesOverrides) { + Class cmdClass = getCmdClass(apiName); + for (RoleType role : RoleType.values()) { + if (commandsPropertiesRoleBasedApisMap.get(role).contains(apiName)) { + // insert permission for this role for this api + addDefaultAclPolicyPermission(apiName, cmdClass, role); + } + } + } + + encodeApiResponse = Boolean.valueOf(_configDao.getValue(Config.EncodeApiResponse.key())); String jsonType = _configDao.getValue(Config.JavaScriptDefaultContentType.key()); if (jsonType != null) { @@ -306,6 +319,74 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer return true; } + private void processMapping(Map configMap) { + for (RoleType roleType : RoleType.values()) { + commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet()); + } + + for (Map.Entry entry : configMap.entrySet()) { + String apiName = entry.getKey(); + String roleMask = entry.getValue(); + commandsPropertiesOverrides.add(apiName); + try { + short cmdPermissions = Short.parseShort(roleMask); + for (RoleType roleType : RoleType.values()) { + if ((cmdPermissions & roleType.getValue()) != 0) + commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName); + } + } catch (NumberFormatException nfe) { + s_logger.info("Malformed key=value pair for entry: " + entry.toString()); + } + } + } + + private void addDefaultAclPolicyPermission(String apiName, Class cmdClass, RoleType role) { + + boolean isReadCommand = false; + if (cmdClass != null) { + BaseCmd cmdObj; + try { + cmdObj = (BaseCmd) cmdClass.newInstance(); + if (cmdObj instanceof BaseListCmd) { + isReadCommand = true; + } + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "%s is claimed as an API command, but it cannot be instantiated", cmdClass.getName())); + } + } + + AclPolicyPermissionVO apiPermission = null; + switch (role) { + case User: + apiPermission = new AclPolicyPermissionVO(role.ordinal() + 1, apiName, null, null, PermissionScope.ACCOUNT, + null, Permission.Allow); + break; + + case Admin: + apiPermission = new AclPolicyPermissionVO(role.ordinal() + 1, apiName, null, null, PermissionScope.ALL, + null, Permission.Allow); + break; + + case DomainAdmin: + apiPermission = new AclPolicyPermissionVO(role.ordinal() + 1, apiName, null, null, PermissionScope.DOMAIN, + null, Permission.Allow); + break; + + case ResourceAdmin: + apiPermission = new AclPolicyPermissionVO(role.ordinal() + 1, apiName, null, null, PermissionScope.DOMAIN, + null, Permission.Allow); + break; + } + + if (apiPermission != null) { + if (isReadCommand) { + apiPermission.setAccessType(AccessType.ListEntry); + } + _aclPermissionDao.persist(apiPermission); + } + } + // NOTE: handle() only handles over the wire (OTW) requests from integration.api.port 8096 // If integration api port is not configured, actual OTW requests will be received by ApiServlet @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -945,7 +1026,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer else { // determine the cmd class based on calling context ResponseView view = ResponseView.Restricted; - if (_accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())) { + if (CallContext.current() != null + && _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())) { view = ResponseView.Full; } for (Class cmdClass : cmdList) { diff --git a/server/src/com/cloud/api/query/vo/AclPolicyJoinVO.java b/server/src/com/cloud/api/query/vo/AclPolicyJoinVO.java index a94ef012e77..3f6eff09dde 100644 --- a/server/src/com/cloud/api/query/vo/AclPolicyJoinVO.java +++ b/server/src/com/cloud/api/query/vo/AclPolicyJoinVO.java @@ -28,7 +28,7 @@ import javax.persistence.Id; import javax.persistence.Table; import org.apache.cloudstack.acl.AclEntityType; -import org.apache.cloudstack.acl.AclPermission; +import org.apache.cloudstack.acl.AclPolicyPermission; import org.apache.cloudstack.acl.PermissionScope; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -95,7 +95,7 @@ public class AclPolicyJoinVO extends BaseViewVO implements ControlledViewEntity @Column(name = "permission_allow_deny") @Enumerated(value = EnumType.STRING) - private AclPermission.Permission permissionAllowDeny; + private AclPolicyPermission.Permission permissionAllowDeny; @Column(name = GenericDao.REMOVED_COLUMN) private Date removed; @@ -206,7 +206,7 @@ public class AclPolicyJoinVO extends BaseViewVO implements ControlledViewEntity return permissionAccessType; } - public AclPermission.Permission getPermissionAllowDeny() { + public AclPolicyPermission.Permission getPermissionAllowDeny() { return permissionAllowDeny; } diff --git a/setup/db/db/schema-421to430.sql b/setup/db/db/schema-421to430.sql index 7d78c9e5686..01c2daf42c5 100644 --- a/setup/db/db/schema-421to430.sql +++ b/setup/db/db/schema-421to430.sql @@ -355,32 +355,23 @@ CREATE TABLE `acl_group_policy_map` ( CONSTRAINT `fk_acl_group_policy_map__policy_id` FOREIGN KEY (`policy_id`) REFERENCES `acl_policy` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE `acl_permission` ( +CREATE TABLE `acl_policy_permission` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `policy_id` bigint(20) unsigned NOT NULL, `action` varchar(100) NOT NULL, - `resource_type` varchar(100) NOT NULL, - `scope_id` bigint(20) unsigned NOT NULL, + `resource_type` varchar(100) DEFAULT NULL, + `scope_id` bigint(20) unsigned, `scope` varchar(40) DEFAULT NULL, - `access_type` varchar(40) NOT NULL, + `access_type` varchar(40) DEFAULT NULL, `permission` varchar(40) NOT NULL COMMENT 'Allow or Deny', `removed` datetime DEFAULT NULL COMMENT 'date the permission was revoked', `created` datetime DEFAULT NULL COMMENT 'date the permission was granted', PRIMARY KEY (`id`), - UNIQUE KEY `id` (`id`) + UNIQUE KEY `id` (`id`), + KEY `fk_acl_policy_permission__policy_id` (`policy_id`), + CONSTRAINT `fk_acl_policy_permission__policy_id` FOREIGN KEY (`policy_id`) REFERENCES `acl_policy` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -CREATE TABLE `acl_policy_permission_map` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `policy_id` bigint(20) unsigned NOT NULL, - `permission_id` bigint(20) unsigned NOT NULL, - `removed` datetime DEFAULT NULL COMMENT 'date the permission was removed from the policy', - `created` datetime DEFAULT NULL COMMENT 'date the permission was added to the policy', - PRIMARY KEY (`id`), - KEY `fk_acl_policy_permission_map__policy_id` (`policy_id`), - KEY `fk_acl_policy_permission_map__permission_id` (`permission_id`), - CONSTRAINT `fk_acl_policy_permission_map__policy_id` FOREIGN KEY (`policy_id`) REFERENCES `acl_policy` (`id`) ON DELETE CASCADE, - CONSTRAINT `fk_acl_policy_permission_map__permission_id` FOREIGN KEY (`permission_id`) REFERENCES `acl_permission` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; INSERT IGNORE INTO `cloud`.`acl_policy` (id, name, description, uuid, domain_id, account_id, created, policy_type) VALUES (1, 'NORMAL', 'Domain user role', UUID(), 1, 1, Now(), 'Static'); INSERT IGNORE INTO `cloud`.`acl_policy` (id, name, description, uuid, domain_id, account_id, created, policy_type) VALUES (2, 'ADMIN', 'Root admin role', UUID(), 1, 1, Now(), 'Static'); @@ -412,12 +403,12 @@ CREATE OR REPLACE VIEW `cloud`.`acl_policy_view` AS account.uuid account_uuid, account.account_name account_name, account.type account_type, - acl_permission.action permission_action, - acl_permission.resource_type permission_entity_type, - acl_permission.scope permission_scope, - acl_permission.scope_id permission_scope_id, - acl_permission.access_type permission_access_type, - acl_permission.permission permission_allow_deny + acl_policy_permission.action permission_action, + acl_policy_permission.resource_type permission_entity_type, + acl_policy_permission.scope permission_scope, + acl_policy_permission.scope_id permission_scope_id, + acl_policy_permission.access_type permission_access_type, + acl_policy_permission.permission permission_allow_deny from `cloud`.`acl_policy` inner join @@ -425,9 +416,7 @@ CREATE OR REPLACE VIEW `cloud`.`acl_policy_view` AS inner join `cloud`.`account` ON acl_policy.account_id = account.id left join - `cloud`.`acl_policy_permission_map` ON acl_policy.id = acl_policy_permission_map.policy_id - left join - `cloud`.`acl_permission` ON acl_permission.id = acl_policy_permission_map.permission_id; + `cloud`.`acl_policy_permission` ON acl_policy.id = acl_policy_permission.policy_id; CREATE OR REPLACE VIEW `cloud`.`acl_group_view` AS @@ -463,8 +452,6 @@ CREATE OR REPLACE VIEW `cloud`.`acl_group_view` AS left join `cloud`.`acl_policy` ON acl_group_policy_map.policy_id = acl_policy.id left join - `cloud`.`acl_policy_permission_map` ON acl_group.id = acl_policy_permission_map.policy_id - left join `cloud`.`acl_group_account_map` ON acl_group.id = acl_group_account_map.group_id left join `cloud`.`account` member_account ON acl_group_account_map.account_id = member_account.id;