From a2056356faabd508ea580679898204a66db62387 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 25 May 2020 17:26:22 +0530 Subject: [PATCH 01/30] Enable role based users in projects --- agent/pom.xml | 2 +- api/pom.xml | 2 +- .../main/java/com/cloud/event/EventTypes.java | 19 +- .../com/cloud/projects/ProjectAccount.java | 4 + .../com/cloud/projects/ProjectService.java | 25 +- .../org/apache/cloudstack/acl/Permission.java | 22 ++ .../apache/cloudstack/acl/ProjectRole.java | 41 +++ .../cloudstack/acl/ProjectRolePermission.java | 24 ++ .../cloudstack/acl/ProjectRoleService.java | 117 +++++++ .../java/org/apache/cloudstack/acl/Role.java | 4 +- .../org/apache/cloudstack/acl/RoleEntity.java | 26 ++ .../apache/cloudstack/acl/RolePermission.java | 10 +- .../cloudstack/acl/RolePermissionEntity.java | 27 ++ .../apache/cloudstack/acl/RoleService.java | 1 - .../apache/cloudstack/api/ApiConstants.java | 5 + .../org/apache/cloudstack/api/BaseCmd.java | 55 ++-- .../admin/acl/BaseRolePermissionCmd.java | 63 ++++ .../admin/acl/CreateRolePermissionCmd.java | 36 +-- .../admin/acl/UpdateRolePermissionCmd.java | 9 +- .../acl/project/CreateProjectRoleCmd.java | 79 +++++ .../CreateProjectRolePermissionCmd.java | 103 ++++++ .../acl/project/DeleteProjectRoleCmd.java | 89 +++++ .../DeleteProjectRolePermissionCmd.java | 88 +++++ .../ListProjectRolePermissionsCmd.java | 112 +++++++ .../acl/project/ListProjectRolesCmd.java | 116 +++++++ .../admin/acl/project/ProjectRoleCmd.java | 62 ++++ .../acl/project/UpdateProjectRoleCmd.java | 84 +++++ .../UpdateProjectRolePermissionCmd.java | 162 ++++++++++ .../user/account/AddAccountToProjectCmd.java | 37 ++- .../user/account/AddUserToProjectCmd.java | 139 ++++++++ .../account/DeleteAccountFromProjectCmd.java | 7 + .../account/DeleteUserFromProjectCmd.java | 117 +++++++ .../user/project/ActivateProjectCmd.java | 9 +- .../user/project/CreateProjectCmd.java | 28 +- .../user/project/DeleteProjectCmd.java | 9 +- .../command/user/project/ListProjectsCmd.java | 2 +- .../user/project/SuspendProjectCmd.java | 9 +- .../user/project/UpdateProjectCmd.java | 70 +++- .../response/BaseRolePermissionResponse.java | 64 ++++ .../api/response/BaseRoleResponse.java | 50 +++ .../ProjectRolePermissionResponse.java | 77 +++++ .../api/response/ProjectRoleResponse.java | 40 +++ .../api/response/RolePermissionResponse.java | 45 +-- .../cloudstack/api/response/RoleResponse.java | 30 +- client/pom.xml | 7 +- core/pom.xml | 2 +- developer/pom.xml | 2 +- engine/api/pom.xml | 2 +- engine/components-api/pom.xml | 2 +- engine/network/pom.xml | 2 +- engine/orchestration/pom.xml | 2 +- engine/pom.xml | 2 +- engine/schema/pom.xml | 2 +- .../com/cloud/projects/ProjectAccountVO.java | 24 +- .../cloud/projects/dao/ProjectAccountDao.java | 8 + .../projects/dao/ProjectAccountDaoImpl.java | 41 ++- .../cloud/upgrade/DatabaseUpgradeChecker.java | 2 + .../upgrade/dao/Upgrade41310to41400.java | 177 ---------- .../upgrade/dao/Upgrade41400to41500.java | 248 ++++++++++++++ .../acl/ProjectRolePermissionVO.java | 66 ++++ .../apache/cloudstack/acl/ProjectRoleVO.java | 100 ++++++ .../cloudstack/acl/RolePermissionBaseVO.java | 96 ++++++ .../cloudstack/acl/RolePermissionVO.java | 73 +---- .../cloudstack/acl/dao/ProjectRoleDao.java | 29 ++ .../acl/dao/ProjectRoleDaoImpl.java | 58 ++++ .../acl/dao/ProjectRolePermissionsDao.java | 34 ++ .../dao/ProjectRolePermissionsDaoImpl.java | 146 +++++++++ .../acl/dao/RolePermissionsDao.java | 7 +- .../acl/dao/RolePermissionsDaoImpl.java | 25 +- ...spring-engine-schema-core-daos-context.xml | 2 + .../db/schema-41400to41500-cleanup.sql | 20 ++ .../META-INF/db/schema-41400to41500.sql | 101 ++++++ engine/service/pom.xml | 2 +- engine/storage/cache/pom.xml | 2 +- engine/storage/configdrive/pom.xml | 2 +- engine/storage/datamotion/pom.xml | 2 +- engine/storage/image/pom.xml | 2 +- engine/storage/integration-test/pom.xml | 2 +- engine/storage/pom.xml | 2 +- engine/storage/snapshot/pom.xml | 2 +- engine/storage/volume/pom.xml | 2 +- framework/agent-lb/pom.xml | 2 +- framework/ca/pom.xml | 2 +- framework/cluster/pom.xml | 2 +- framework/config/pom.xml | 2 +- framework/db/pom.xml | 2 +- framework/direct-download/pom.xml | 2 +- framework/events/pom.xml | 2 +- framework/ipc/pom.xml | 2 +- framework/jobs/pom.xml | 2 +- framework/managed-context/pom.xml | 2 +- framework/pom.xml | 2 +- framework/quota/pom.xml | 2 +- framework/rest/pom.xml | 2 +- framework/security/pom.xml | 2 +- framework/spring/lifecycle/pom.xml | 2 +- framework/spring/module/pom.xml | 2 +- plugins/acl/dynamic-role-based/pom.xml | 2 +- .../acl/DynamicRoleBasedAPIAccessChecker.java | 9 +- .../DynamicRoleBasedAPIAccessCheckerTest.java | 28 +- plugins/acl/project-role-based/pom.xml | 30 ++ .../acl/ProjectRoleBasedApiAccessChecker.java | 139 ++++++++ .../acl-project-role-based/module.properties | 18 ++ .../spring-acl-project-role-based-context.xml | 33 ++ plugins/acl/static-role-based/pom.xml | 2 +- .../explicit-dedication/pom.xml | 2 +- .../host-affinity/pom.xml | 2 +- .../host-anti-affinity/pom.xml | 2 +- plugins/alert-handlers/snmp-alerts/pom.xml | 2 +- plugins/alert-handlers/syslog-alerts/pom.xml | 2 +- plugins/api/discovery/pom.xml | 2 +- .../discovery/ApiDiscoveryServiceImpl.java | 32 +- plugins/api/rate-limit/pom.xml | 2 +- plugins/api/solidfire-intg-test/pom.xml | 2 +- plugins/api/vmware-sioc/pom.xml | 2 +- plugins/backup/dummy/pom.xml | 2 +- plugins/backup/veeam/pom.xml | 2 +- plugins/ca/root-ca/pom.xml | 2 +- plugins/database/mysql-ha/pom.xml | 2 +- plugins/database/quota/pom.xml | 2 +- plugins/dedicated-resources/pom.xml | 2 +- .../implicit-dedication/pom.xml | 2 +- .../user-concentrated-pod/pom.xml | 2 +- .../user-dispersing/pom.xml | 2 +- plugins/event-bus/inmemory/pom.xml | 2 +- plugins/event-bus/kafka/pom.xml | 2 +- plugins/event-bus/rabbitmq/pom.xml | 2 +- plugins/ha-planners/skip-heurestics/pom.xml | 2 +- plugins/host-allocators/random/pom.xml | 2 +- plugins/hypervisors/baremetal/pom.xml | 32 +- plugins/hypervisors/hyperv/pom.xml | 2 +- plugins/hypervisors/kvm/pom.xml | 2 +- plugins/hypervisors/ovm/pom.xml | 2 +- plugins/hypervisors/ovm3/pom.xml | 2 +- plugins/hypervisors/simulator/pom.xml | 2 +- plugins/hypervisors/ucs/pom.xml | 2 +- plugins/hypervisors/vmware/pom.xml | 2 +- plugins/hypervisors/xenserver/pom.xml | 2 +- plugins/integrations/cloudian/pom.xml | 2 +- .../integrations/kubernetes-service/pom.xml | 2 +- plugins/integrations/prometheus/pom.xml | 2 +- plugins/metrics/pom.xml | 2 +- plugins/network-elements/bigswitch/pom.xml | 2 +- plugins/network-elements/brocade-vcs/pom.xml | 2 +- plugins/network-elements/cisco-vnmc/pom.xml | 2 +- plugins/network-elements/dns-notifier/pom.xml | 2 +- .../elastic-loadbalancer/pom.xml | 2 +- plugins/network-elements/f5/pom.xml | 2 +- plugins/network-elements/globodns/pom.xml | 2 +- .../internal-loadbalancer/pom.xml | 2 +- .../network-elements/juniper-contrail/pom.xml | 2 +- plugins/network-elements/juniper-srx/pom.xml | 2 +- plugins/network-elements/netscaler/pom.xml | 2 +- plugins/network-elements/nicira-nvp/pom.xml | 2 +- plugins/network-elements/opendaylight/pom.xml | 2 +- plugins/network-elements/ovs/pom.xml | 2 +- plugins/network-elements/palo-alto/pom.xml | 2 +- .../network-elements/stratosphere-ssp/pom.xml | 2 +- plugins/network-elements/vxlan/pom.xml | 2 +- .../ipmitool/pom.xml | 2 +- .../nested-cloudstack/pom.xml | 2 +- plugins/pom.xml | 3 +- plugins/storage-allocators/random/pom.xml | 2 +- plugins/storage/image/default/pom.xml | 2 +- plugins/storage/image/s3/pom.xml | 2 +- plugins/storage/image/sample/pom.xml | 2 +- plugins/storage/image/swift/pom.xml | 2 +- plugins/storage/volume/cloudbyte/pom.xml | 2 +- plugins/storage/volume/datera/pom.xml | 2 +- plugins/storage/volume/default/pom.xml | 2 +- plugins/storage/volume/nexenta/pom.xml | 2 +- plugins/storage/volume/sample/pom.xml | 2 +- plugins/storage/volume/solidfire/pom.xml | 2 +- plugins/user-authenticators/ldap/pom.xml | 2 +- plugins/user-authenticators/md5/pom.xml | 2 +- plugins/user-authenticators/pbkdf2/pom.xml | 2 +- .../user-authenticators/plain-text/pom.xml | 2 +- plugins/user-authenticators/saml2/pom.xml | 2 +- .../user-authenticators/sha256salted/pom.xml | 2 +- pom.xml | 2 +- quickcloud/pom.xml | 2 +- server/pom.xml | 2 +- .../java/com/cloud/acl/DomainChecker.java | 40 ++- .../main/java/com/cloud/api/ApiDBUtils.java | 1 + .../java/com/cloud/api/ApiResponseHelper.java | 10 +- .../main/java/com/cloud/api/ApiServer.java | 176 +++++----- .../api/dispatch/ParamProcessWorker.java | 15 +- .../cloud/api/query/ViewResponseHelper.java | 8 +- .../cloud/projects/ProjectManagerImpl.java | 287 +++++++++++++--- .../cloud/server/ManagementServerImpl.java | 8 +- .../com/cloud/user/AccountManagerImpl.java | 46 ++- .../acl/ProjectRoleManagerImpl.java | 306 ++++++++++++++++++ .../cloudstack/acl/RoleManagerImpl.java | 10 +- .../spring-server-core-managers-context.xml | 2 + .../projects/MockProjectManagerImpl.java | 26 +- .../cloud/user/AccountManagerImplTest.java | 16 +- services/console-proxy/pom.xml | 2 +- services/console-proxy/rdpconsole/pom.xml | 2 +- services/console-proxy/server/pom.xml | 2 +- services/pom.xml | 2 +- services/secondary-storage/controller/pom.xml | 2 +- services/secondary-storage/pom.xml | 2 +- services/secondary-storage/server/pom.xml | 2 +- systemvm/pom.xml | 2 +- test/pom.xml | 2 +- tools/apidoc/pom.xml | 2 +- tools/checkstyle/pom.xml | 2 +- tools/devcloud-kvm/pom.xml | 2 +- tools/devcloud4/pom.xml | 2 +- tools/marvin/pom.xml | 2 +- tools/marvin/setup.py | 2 +- tools/pom.xml | 2 +- usage/pom.xml | 2 +- utils/pom.xml | 2 +- vmware-base/pom.xml | 2 +- 215 files changed, 4149 insertions(+), 766 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/acl/Permission.java create mode 100644 api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java create mode 100644 api/src/main/java/org/apache/cloudstack/acl/ProjectRolePermission.java create mode 100644 api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java create mode 100644 api/src/main/java/org/apache/cloudstack/acl/RoleEntity.java create mode 100644 api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java create mode 100644 engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java create mode 100644 engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql create mode 100644 engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql create mode 100644 plugins/acl/project-role-based/pom.xml create mode 100644 plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java create mode 100644 plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties create mode 100644 plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/spring-acl-project-role-based-context.xml create mode 100644 server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java diff --git a/agent/pom.xml b/agent/pom.xml index a9154cc9247c..9ccd377a9b82 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/api/pom.xml b/api/pom.xml index fdee3d8879af..e9a0115b2b51 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 30b6ac0b0a17..2ccbfc9e2173 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -22,6 +22,10 @@ import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; import org.apache.cloudstack.annotation.Annotation; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.ha.HAConfig; import org.apache.cloudstack.usage.Usage; @@ -76,10 +80,6 @@ import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.VirtualMachine; -import org.apache.cloudstack.api.response.ClusterResponse; -import org.apache.cloudstack.api.response.HostResponse; -import org.apache.cloudstack.api.response.PodResponse; -import org.apache.cloudstack.api.response.ZoneResponse; public class EventTypes { @@ -188,6 +188,15 @@ public class EventTypes { public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE"; public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE"; + // Project Role events + public static final String EVENT_PROJECT_ROLE_CREATE = "PROJECT.ROLE.CREATE"; + public static final String EVENT_PROJECT_ROLE_UPDATE = "PROJECT.ROLE.UPDATE"; + public static final String EVENT_PROJECT_ROLE_DELETE = "PROJECT.ROLE.DELETE"; + public static final String EVENT_PROJECT_ROLE_PERMISSION_CREATE = "PROJECT.ROLE.PERMISSION.CREATE"; + public static final String EVENT_PROJECT_ROLE_PERMISSION_UPDATE_ORDER = "PROJECT.ROLE.PERMISSION.UPDATE"; + public static final String EVENT_PROJECT_ROLE_PERMISSION_UPDATE = "PROJECT.ROLE.PERMISSION.UPDATE"; + public static final String EVENT_PROJECT_ROLE_PERMISSION_DELETE = "PROJECT.ROLE.PERMISSION.DELETE"; + // CA events public static final String EVENT_CA_CERTIFICATE_ISSUE = "CA.CERTIFICATE.ISSUE"; public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE"; @@ -397,9 +406,11 @@ public class EventTypes { public static final String EVENT_PROJECT_ACTIVATE = "PROJECT.ACTIVATE"; public static final String EVENT_PROJECT_SUSPEND = "PROJECT.SUSPEND"; public static final String EVENT_PROJECT_ACCOUNT_ADD = "PROJECT.ACCOUNT.ADD"; + public static final String EVENT_PROJECT_USER_ADD = "PROJECT.USER.ADD"; public static final String EVENT_PROJECT_INVITATION_UPDATE = "PROJECT.INVITATION.UPDATE"; public static final String EVENT_PROJECT_INVITATION_REMOVE = "PROJECT.INVITATION.REMOVE"; public static final String EVENT_PROJECT_ACCOUNT_REMOVE = "PROJECT.ACCOUNT.REMOVE"; + public static final String EVENT_PROJECT_USER_REMOVE = "PROJECT.USER.REMOVE"; // Network as a Service public static final String EVENT_NETWORK_ELEMENT_CONFIGURE = "NETWORK.ELEMENT.CONFIGURE"; diff --git a/api/src/main/java/com/cloud/projects/ProjectAccount.java b/api/src/main/java/com/cloud/projects/ProjectAccount.java index b8cf3f18229b..8025732a6e90 100644 --- a/api/src/main/java/com/cloud/projects/ProjectAccount.java +++ b/api/src/main/java/com/cloud/projects/ProjectAccount.java @@ -23,8 +23,12 @@ public enum Role { long getAccountId(); + Long getUserId(); + long getProjectId(); + Long getProjectRoleId(); + Role getAccountRole(); long getProjectAccountId(); diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index dc882ef11b8d..422e724251df 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.projects; +import java.util.List; + import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; @@ -34,10 +36,17 @@ public interface ProjectService { * - account name of the project owner * @param domainId * - domainid of the project owner + * + * @param userId + * - id of the user to be made as project owner + * + * @param accountId + * - id of the account to which the user belongs + * * @return the project if created successfully, null otherwise * @throws ResourceAllocationException */ - Project createProject(String name, String displayText, String accountName, Long domainId) throws ResourceAllocationException; + Project createProject(String name, String displayText, String accountName, Long domainId, Long userId, Long accountId) throws ResourceAllocationException; /** * Deletes a project @@ -57,22 +66,28 @@ public interface ProjectService { */ Project getProject(long id); - ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole); + ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole, Long userId, Long projectRoleId); Account getProjectOwner(long projectId); + List getProjectOwners(long projectId); + boolean unassignAccountFromProject(long projectId, long accountId); Project findByProjectAccountId(long projectAccountId); Project findByNameAndDomainId(String name, long domainId); - Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; + //Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; + + Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException; - boolean addAccountToProject(long projectId, String accountName, String email); + boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType); boolean deleteAccountFromProject(long projectId, String accountName); + boolean deleteUserFromProject(long projectId, long userId); + boolean updateInvitation(long projectId, String accountName, String token, boolean accept); Project activateProject(long projectId); @@ -84,4 +99,6 @@ public interface ProjectService { boolean deleteProjectInvitation(long invitationId); Project findByProjectAccountIdIncludingRemoved(long projectAccountId); + + boolean addUserToProject(Long projectId, Long userId, Long projectRoleId, Role projectRole); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/Permission.java b/api/src/main/java/org/apache/cloudstack/acl/Permission.java new file mode 100644 index 000000000000..032b40ae6cdc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/Permission.java @@ -0,0 +1,22 @@ +// 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.acl; + +public enum Permission { + ALLOW, DENY +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java b/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java new file mode 100644 index 000000000000..a64011294291 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java @@ -0,0 +1,41 @@ +// 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.acl; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import com.google.common.base.Enums; +import com.google.common.base.Strings; + +public interface ProjectRole extends RoleEntity, InternalIdentity, Identity { + + Long getProjectId(); + + public enum ProjectRoleType { + Admin, Regular; + + public static ProjectRoleType fromString(final String name) { + if (!Strings.isNullOrEmpty(name) + && Enums.getIfPresent(RoleType.class, name).isPresent()) { + return ProjectRoleType.valueOf(name); + } + throw new IllegalStateException("Illegal ProjectRoleType name provided"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/ProjectRolePermission.java b/api/src/main/java/org/apache/cloudstack/acl/ProjectRolePermission.java new file mode 100644 index 000000000000..ae6d78c6a4d7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRolePermission.java @@ -0,0 +1,24 @@ +// 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.acl; + +public interface ProjectRolePermission extends RolePermissionEntity { + long getProjectRoleId(); + long getProjectId(); + long getSortOrder(); +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java b/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java new file mode 100644 index 000000000000..fd8c2961fbac --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java @@ -0,0 +1,117 @@ +// 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.acl; + +import java.util.List; + +import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRolePermissionCmd; + +public interface ProjectRoleService { + /** + * Creates a Project role in a Project to be mapped to a user/ account (all users of an account) + * @param projectId ID of the project where the project role is to be created + * @param name Name of the project role + * @param description description provided for the project role + * @return the Instance of the project role created + */ + ProjectRole createProjectRole(Long projectId, String name, String description); + + /** + * Updates a Project role created + * @param role Project role reference to be updated + * @param projectId ID of the project where the Project role exists + * @param name new name to be given to the project role + * @param description description for the project role + * @return the updated instance of the project role + */ + ProjectRole updateProjectRole(ProjectRole role, Long projectId, String name, String description); + + /** + * + * @param projectId ID of the project in which the project role is to be searched for + * @param roleName name/ part of a project role name + * @return List of Project roles matching the given name in the project + */ + List findProjectRolesByName(Long projectId, String roleName); + + /** + * + * @param role Project role to be deleted + * @param projectId ID of the project where the role is present + * @return success/failure of the delete operation + */ + boolean deleteProjectRole(ProjectRole role, Long projectId); + + /** + * Determines if Dynamic Roles feature is enabled , if it isn't then the project roles will not be applied + */ + boolean isEnabled(); + + /** + * + * @param roleId Project role ID which needs to be found + * @param projectId ID of the project where the role is to be found + * @return the corresponding project role + */ + ProjectRole findProjectRole(Long roleId, Long projectId); + + /** + * + * @param projectId ID of the project whosr project roles are to be listed + * @return List of all available project roles + */ + List findProjectRoles(Long projectId); + + /** + * Creates a project role permission to be mapped to a project role. + * All accounts/users mapped to this project role will impose restrictions on API access + * to users based on the project role. This is to further limit restrictions on users in projects + */ + ProjectRolePermission createProjectRolePermission(CreateProjectRolePermissionCmd cmd); + + /** + * Updates the order of the project role permission + * @param projectId ID of the project where the project role permission exists + * @param projectRole project role to which the permission is mapped to + * @param rolePermissionsOrder re-arranged order of permissions + * @return success/failure of operation + */ + boolean updateProjectRolePermission(Long projectId, ProjectRole projectRole, List rolePermissionsOrder); + + /** + * + * Updates the permission of the project role permission + */ + boolean updateProjectRolePermission(Long projectId, ProjectRole projectRole, ProjectRolePermission projectRolePermission, Permission newPermission); + + /** + * Finds the project role permission for the given ID + */ + ProjectRolePermission findProjectRolePermission(final Long projRolePermissionId); + + /** + * deletes the given project role + */ + boolean deleteProjectRolePermission(ProjectRolePermission projectRolePermission); + + /** + * returns list of all project role permissions mapped to the requested project role + */ + List findAllProjectRolePermissions(Long projectId, Long projectRoleId); + +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/Role.java b/api/src/main/java/org/apache/cloudstack/acl/Role.java index b05d886fdbfc..f03520496e81 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/Role.java +++ b/api/src/main/java/org/apache/cloudstack/acl/Role.java @@ -20,8 +20,6 @@ import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -public interface Role extends InternalIdentity, Identity { - String getName(); +public interface Role extends RoleEntity, InternalIdentity, Identity { RoleType getRoleType(); - String getDescription(); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleEntity.java b/api/src/main/java/org/apache/cloudstack/acl/RoleEntity.java new file mode 100644 index 000000000000..dc254188755d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleEntity.java @@ -0,0 +1,26 @@ +// 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.acl; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface RoleEntity extends InternalIdentity, Identity { + String getName(); + String getDescription(); +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/RolePermission.java b/api/src/main/java/org/apache/cloudstack/acl/RolePermission.java index 0157a6682560..9cc01e93884c 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RolePermission.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RolePermission.java @@ -17,15 +17,7 @@ package org.apache.cloudstack.acl; -import org.apache.cloudstack.api.Identity; -import org.apache.cloudstack.api.InternalIdentity; - -public interface RolePermission extends InternalIdentity, Identity { - enum Permission {ALLOW, DENY} - +public interface RolePermission extends RolePermissionEntity { long getRoleId(); - Rule getRule(); - Permission getPermission(); - String getDescription(); long getSortOrder(); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java new file mode 100644 index 000000000000..25abdb3a6ce8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java @@ -0,0 +1,27 @@ +// 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.acl; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface RolePermissionEntity extends InternalIdentity, Identity { + Rule getRule(); + Permission getPermission(); + String getDescription(); +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java index 6130c62a7d45..ef62b2d95ad0 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -19,7 +19,6 @@ import java.util.List; -import org.apache.cloudstack.acl.RolePermission.Permission; import org.apache.cloudstack.framework.config.ConfigKey; public interface RoleService { diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 2a201dba196a..ecdabf9ded95 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -471,11 +471,16 @@ public class ApiConstants { public static final String PROJECT = "project"; public static final String ROLE = "role"; public static final String ROLE_ID = "roleid"; + public static final String ACCOUNT_ROLE_TYPE = "accountroletype"; + public static final String PROJECT_ROLE_ID = "projectroleid"; + public static final String PROJECT_ROLE_NAME = "projectrolename"; + public static final String PROJECT_ROLE_TYPE = "projectroletype"; public static final String ROLE_TYPE = "roletype"; public static final String ROLE_NAME = "rolename"; public static final String PERMISSION = "permission"; public static final String RULE = "rule"; public static final String RULE_ID = "ruleid"; + public static final String PROJECT_RULE_ID = "projectruleid"; public static final String RULE_ORDER = "ruleorder"; public static final String USER = "user"; public static final String ACTIVE_ONLY = "activeonly"; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index 37dbeaab841a..5b4c7aa3ef8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -17,6 +17,32 @@ package org.apache.cloudstack.api; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.ProjectRoleService; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.affinity.AffinityGroupService; +import org.apache.cloudstack.alert.AlertService; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; +import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; +import org.apache.cloudstack.query.QueryService; +import org.apache.cloudstack.usage.UsageService; +import org.apache.log4j.Logger; + import com.cloud.configuration.ConfigurationService; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -58,29 +84,6 @@ import com.cloud.utils.db.UUIDManager; import com.cloud.vm.UserVmService; import com.cloud.vm.snapshot.VMSnapshotService; -import org.apache.cloudstack.acl.RoleService; -import org.apache.cloudstack.acl.RoleType; -import org.apache.cloudstack.affinity.AffinityGroupService; -import org.apache.cloudstack.alert.AlertService; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; -import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService; -import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; -import org.apache.cloudstack.query.QueryService; -import org.apache.cloudstack.usage.UsageService; -import org.apache.log4j.Logger; - -import javax.inject.Inject; -import java.lang.reflect.Field; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; public abstract class BaseCmd { private static final Logger s_logger = Logger.getLogger(BaseCmd.class.getName()); @@ -112,6 +115,8 @@ public static enum CommandType { @Inject public RoleService roleService; @Inject + public ProjectRoleService projRoleService; + @Inject public UserVmService _userVmService; @Inject public ManagementService _mgr; @@ -264,6 +269,10 @@ public String getActualCommandName() { */ public abstract long getEntityOwnerId(); + public List getEntityOwnerIds() { + return null; + } + public Object getResponseObject() { return _responseObject; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java new file mode 100644 index 000000000000..40a5d6553dbc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java @@ -0,0 +1,63 @@ +// 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.api.command.admin.acl; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.Rule; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; + +import com.google.common.base.Strings; + +public abstract class BaseRolePermissionCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.RULE, type = CommandType.STRING, required = true, description = "The API name or wildcard rule such as list*", + validations = {ApiArgValidator.NotNullOrEmpty}) + private String rule; + + @Parameter(name = ApiConstants.PERMISSION, type = CommandType.STRING, required = true, description = "The rule permission, allow or deny. Default: deny.") + private String permission; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role permission") + private String description; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Rule getRule() { + return new Rule(rule); + } + + public Permission getPermission() { + if (Strings.isNullOrEmpty(permission)) { + return null; + } + return Permission.valueOf(permission.toUpperCase()); + } + + public String getDescription() { + return description; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java index aeb3f4ee11ad..d7d6ef125df9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java @@ -17,28 +17,27 @@ package org.apache.cloudstack.api.command.admin.acl; -import com.cloud.user.Account; -import com.google.common.base.Strings; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; import org.apache.cloudstack.acl.RoleType; -import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.response.RolePermissionResponse; import org.apache.cloudstack.api.response.RoleResponse; import org.apache.cloudstack.context.CallContext; -@APICommand(name = CreateRolePermissionCmd.APINAME, description = "Adds a API permission to a role", responseObject = RolePermissionResponse.class, +import com.cloud.user.Account; + +@APICommand(name = CreateRolePermissionCmd.APINAME, description = "Adds an API permission to a role", responseObject = RolePermissionResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.9.0", authorized = {RoleType.Admin}) -public class CreateRolePermissionCmd extends BaseCmd { +public class CreateRolePermissionCmd extends BaseRolePermissionCmd { public static final String APINAME = "createRolePermission"; ///////////////////////////////////////////////////// @@ -49,16 +48,6 @@ public class CreateRolePermissionCmd extends BaseCmd { description = "ID of the role", validations = {ApiArgValidator.PositiveNumber}) private Long roleId; - @Parameter(name = ApiConstants.RULE, type = CommandType.STRING, required = true, description = "The API name or wildcard rule such as list*", - validations = {ApiArgValidator.NotNullOrEmpty}) - private String rule; - - @Parameter(name = ApiConstants.PERMISSION, type = CommandType.STRING, required = true, description = "The rule permission, allow or deny. Default: deny.") - private String permission; - - @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role permission") - private String description; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -67,21 +56,6 @@ public Long getRoleId() { return roleId; } - public Rule getRule() { - return new Rule(rule); - } - - public RolePermission.Permission getPermission() { - if (Strings.isNullOrEmpty(permission)) { - return null; - } - return RolePermission.Permission.valueOf(permission.toUpperCase()); - } - - public String getDescription() { - return description; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java index 045464eab79a..026533aebed5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java @@ -17,10 +17,12 @@ package org.apache.cloudstack.api.command.admin.acl; -import com.cloud.user.Account; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.RolePermission.Permission; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; @@ -34,8 +36,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; -import java.util.ArrayList; -import java.util.List; +import com.cloud.user.Account; @APICommand(name = UpdateRolePermissionCmd.APINAME, description = "Updates a role permission order", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java new file mode 100644 index 000000000000..d0a02e9c8e5e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java @@ -0,0 +1,79 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.user.Account; + +@APICommand(name = CreateProjectRoleCmd.APINAME, description = "Creates a Project role", responseObject = ProjectRoleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class CreateProjectRoleCmd extends ProjectRoleCmd { + public static final String APINAME = "createProjectRole"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true, + description = "creates a project role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty}) + private String projectRoleName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getProjectRoleName() { + return projectRoleName; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + CallContext.current().setEventDetails("Role: " + getProjectRoleName() + ", description: " + getProjectRoleDescription()); + ProjectRole projectRole = projRoleService.createProjectRole(getProjectId(), getProjectRoleName(), getProjectRoleDescription()); + if (projectRole == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create project role"); + } + setupProjectRoleResponse(projectRole); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java new file mode 100644 index 000000000000..e6e11da195fb --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java @@ -0,0 +1,103 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.acl.BaseRolePermissionCmd; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRolePermissionResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = CreateProjectRolePermissionCmd.APINAME, description = "Adds API permissions to a project role", responseObject = ProjectRolePermissionResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class CreateProjectRolePermissionCmd extends BaseRolePermissionCmd { + public static final String APINAME = "createProjectRolePermission"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRoleId; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, required = true, entityType = ProjectResponse.class, + description = "ID of project where project role permission is to be created", validations = {ApiArgValidator.NotNullOrEmpty}) + private Long projectId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectRoleId() { + return projectRoleId; + } + + public Long getProjectId() { + return projectId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ProjectRole projectRole = projRoleService.findProjectRole(getProjectRoleId(), getProjectId()); + if (projectRole == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid project role ID provided"); + } + CallContext.current().setEventDetails("Project Role ID: " + projectRole.getId() + ", Rule:" + getRule() + ", Permission: " + getPermission() + ", Description: " + getDescription()); + final ProjectRolePermission projectRolePermission = projRoleService.createProjectRolePermission(this); + if (projectRolePermission == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create project role permission"); + } + setupResponse(projectRolePermission, projectRole); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + + private void setupResponse(final ProjectRolePermission rolePermission, final ProjectRole role) { + final ProjectRolePermissionResponse response = new ProjectRolePermissionResponse(); + response.setId(rolePermission.getUuid()); + response.setProjectRoleId(role.getUuid()); + response.setRule(rolePermission.getRule()); + response.setRulePermission(rolePermission.getPermission()); + response.setDescription(rolePermission.getDescription()); + response.setResponseName(getCommandName()); + response.setObjectName("projectrolepermission"); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java new file mode 100644 index 000000000000..ec5140f83de4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java @@ -0,0 +1,89 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + + +@APICommand(name = DeleteProjectRoleCmd.APINAME, description = "Delete Project roles in CloudStack", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.15.0", authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin}) +public class DeleteProjectRoleCmd extends BaseCmd { + public static final String APINAME = "deleteProjectRole" ; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + description = "ID of the project role to be deleted", validations = {ApiArgValidator.PositiveNumber}) + private Long id; + + @Parameter(name = ApiConstants.PROJECT_ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectResponse.class, + description = "ID of the project from where the role is to be deleted", validations = {ApiArgValidator.PositiveNumber}) + private Long projectId; + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getProjectId() { return projectId; } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + @Override + public void execute() { + ProjectRole role = projRoleService.findProjectRole(getId(), getProjectId()); + if (role == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find project role with provided id"); + } + CallContext.current().setEventDetails("Deleting Project Role with id: " + role.getId()); + boolean result = projRoleService.deleteProjectRole(role, getProjectId()); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java new file mode 100644 index 000000000000..38cb6ab1442c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java @@ -0,0 +1,88 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRolePermissionResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = DeleteProjectRolePermissionCmd.APINAME, description = "Deletes a project role permission in the project", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class DeleteProjectRolePermissionCmd extends BaseCmd { + public static final String APINAME = "deleteProjectRolePermission"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectResponse.class, + description = "ID of the project where the project role permission is to be deleted", validations = {ApiArgValidator.PositiveNumber}) + private Long projectId; + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectRolePermissionResponse.class, + description = "ID of the project role permission to be deleted", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRolePermissionId; + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public Long getProjectRolePermissionId() { + return projectRolePermissionId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ProjectRolePermission rolePermission = projRoleService.findProjectRolePermission(getProjectRolePermissionId()); + if (rolePermission == null || rolePermission.getProjectId() != getProjectId()) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role permission id provided for the project"); + } + CallContext.current().setEventDetails("Deleting Project Role permission with id: " + rolePermission.getId()); + boolean result = projRoleService.deleteProjectRolePermission(rolePermission); + SuccessResponse response = new SuccessResponse(); + response.setSuccess(result); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java new file mode 100644 index 000000000000..16f84b5cc528 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java @@ -0,0 +1,112 @@ +// 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.api.command.admin.acl.project; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRolePermissionResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = ListProjectRolePermissionsCmd.APINAME, description = "Lists a project's project role permissions", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class ListProjectRolePermissionsCmd extends BaseCmd { + public static final String APINAME = "listProjectRolePermissions"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "ID of the project") + private Long projectId; + + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, entityType = ProjectRoleResponse.class, + description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRoleId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public Long getProjectRoleId() { + return projectRoleId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + @Override + public void execute() { + List projectRolePermissions = projRoleService.findAllProjectRolePermissions(getProjectId(), getProjectRoleId()); + final ProjectRole projectRole = projRoleService.findProjectRole(getProjectRoleId(), getProjectId()); + final ListResponse response = new ListResponse<>(); + final List rolePermissionResponses = new ArrayList<>(); + for (final ProjectRolePermission rolePermission : projectRolePermissions) { + ProjectRole role = projectRole; + if (role == null) { + role = projRoleService.findProjectRole(rolePermission.getProjectRoleId(), rolePermission.getProjectId()); + } + rolePermissionResponses.add(setupResponse(role, rolePermission)); + } + response.setResponses(rolePermissionResponses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + private ProjectRolePermissionResponse setupResponse(ProjectRole role, ProjectRolePermission rolePermission) { + final ProjectRolePermissionResponse rolePermissionResponse = new ProjectRolePermissionResponse(); + rolePermissionResponse.setProjectId((rolePermission.getProjectId())); + rolePermissionResponse.setProjectRoleId(role.getUuid()); + rolePermissionResponse.setProjectRoleName(role.getName()); + rolePermissionResponse.setId(rolePermission.getUuid()); + rolePermissionResponse.setRule(rolePermission.getRule()); + rolePermissionResponse.setRulePermission(rolePermission.getPermission()); + rolePermissionResponse.setDescription(rolePermission.getDescription()); + rolePermissionResponse.setObjectName("rolepermission"); + return rolePermissionResponse; + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java new file mode 100644 index 000000000000..efa471aae94f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java @@ -0,0 +1,116 @@ +// 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.api.command.admin.acl.project; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; + +@APICommand(name = ListProjectRolesCmd.APINAME, description = "Lists Project roles in CloudStack", responseObject = ProjectRoleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.15.0", authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin}) +public class ListProjectRolesCmd extends BaseCmd { + public static final String APINAME = "listProjectRoles"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, entityType = ProjectRoleResponse.class, description = "List project role by project role ID.") + private Long projectRoleId; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "List project role by project ID.") + private Long projectId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List project role by project role name.") + private String roleName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getProjectRoleId() { return projectRoleId; } + + public Long getProjectId() { + return projectId; + } + + public String getRoleName() { + return roleName; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + List projectRoles; + if (getProjectId() != null && getProjectRoleId() != null) { + projectRoles = Collections.singletonList(projRoleService.findProjectRole(getProjectRoleId(), getProjectId())); + } else if (StringUtils.isNotBlank(getRoleName())) { + projectRoles = projRoleService.findProjectRolesByName(getProjectId(), getRoleName()); + } else { + projectRoles = projRoleService.findProjectRoles(getProjectId()); + } + final ListResponse response = new ListResponse<>(); + final List roleResponses = new ArrayList<>(); + for (ProjectRole role : projectRoles) { + if (role == null) { + continue; + } + roleResponses.add(setupProjectRoleResponse(role)); + } + response.setResponses(roleResponses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + private ProjectRoleResponse setupProjectRoleResponse(final ProjectRole role) { + final ProjectRoleResponse response = new ProjectRoleResponse(); + response.setId(role.getUuid()); + response.setProjectId(role.getProjectId()); + response.setRoleName(role.getName()); + response.setDescription(role.getDescription()); + response.setObjectName("projectrole"); + return response; + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java new file mode 100644 index 000000000000..0026ec5e782c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java @@ -0,0 +1,62 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; + +public abstract class ProjectRoleCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, required = true, entityType = ProjectResponse.class, + description = "ID of project where role is being created", validations = {ApiArgValidator.NotNullOrEmpty}) + private Long projectId; + + @Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "The description of the Project role") + private String projectRoleDescription; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public String getProjectRoleDescription() { + return projectRoleDescription; + } + + protected void setupProjectRoleResponse(final ProjectRole role) { + final ProjectRoleResponse response = new ProjectRoleResponse(); + response.setId(role.getUuid()); + response.setProjectId(role.getProjectId()); + response.setRoleName(role.getName()); + response.setDescription(role.getDescription()); + response.setResponseName(getCommandName()); + response.setObjectName("projectrole"); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java new file mode 100644 index 000000000000..5577d3d317b7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java @@ -0,0 +1,84 @@ +// 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.api.command.admin.acl.project; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectRoleResponse; + +@APICommand(name = UpdateProjectRoleCmd.APINAME, description = "Creates a Project role", responseObject = ProjectRoleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class UpdateProjectRoleCmd extends ProjectRoleCmd { + public static final String APINAME = "updateProjectRole"; + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + description = "ID of the Project role", validations = {ApiArgValidator.PositiveNumber}) + private Long id; + + @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, + description = "creates a project role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty}) + private String projectRoleName; + + ///////////////////////////////////////////////////// + //////////////// Accessors ////////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getProjectRoleName() { + return projectRoleName; + } + + ///////////////////////////////////////////////////// + //////////////// API Implementation ///////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ProjectRole role = projRoleService.findProjectRole(getId(), getProjectId()); + if (role == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid project role id provided"); + } + role = projRoleService.updateProjectRole(role, getProjectId(), getProjectRoleName(), getProjectRoleDescription()); + setupProjectRoleResponse(role); + + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return 0; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java new file mode 100644 index 000000000000..2891cb270330 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java @@ -0,0 +1,162 @@ +// 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.api.command.admin.acl.project; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRolePermissionResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.EnumUtils; + +@APICommand(name = UpdateProjectRolePermissionCmd.APINAME, description = "Updates a project role permission and/or order", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0") +public class UpdateProjectRolePermissionCmd extends BaseCmd { + public static final String APINAME = "updateProjectRolePermission"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRoleId; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, required = true, entityType = ProjectResponse.class, + description = "ID of project where project role permission is to be updated", validations = {ApiArgValidator.NotNullOrEmpty}) + private Long projectId; + + @Parameter(name = ApiConstants.RULE_ORDER, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ProjectRolePermissionResponse.class, + description = "The parent role permission uuid, use 0 to move this rule at the top of the list") + private List projectRulePermissionOrder; + + @Parameter(name = ApiConstants.PROJECT_RULE_ID, type = CommandType.UUID, entityType = ProjectRolePermissionResponse.class, + description = "Project Role permission rule id", since="4.11") + private Long projectRuleId; + + @Parameter(name = ApiConstants.PERMISSION, type = CommandType.STRING, + description = "Rule permission, can be: allow or deny", since="4.11") + private String projectRolePermission; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getProjectRoleId() { + return projectRoleId; + } + + public List getProjectRulePermissionOrder() { + return projectRulePermissionOrder; + } + + public Long getProjectRuleId() { + return projectRuleId; + } + + public Permission getProjectRolePermission() { + if (this.projectRolePermission == null) { + return null; + } + + if (!EnumUtils.isValidEnum(Permission.class, projectRolePermission.toUpperCase())) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Values for permission parameter should be: allow or deny"); + } + + return Permission.valueOf(projectRolePermission.toUpperCase()); + } + + public Long getProjectId() { + return projectId; + } + + ///////////////////////////////////////////////////// + /////////////////// API Implementation ////////////// + ///////////////////////////////////////////////////// + @Override + public void execute() { + ProjectRole projectRole = projRoleService.findProjectRole(getProjectRoleId(), getProjectId()); + boolean result = false; + if (projectRole == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided"); + } + if (getProjectRulePermissionOrder() != null) { + if (getProjectRuleId() != null || getProjectRolePermission() != null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Parameters permission and ruleid must be mutually exclusive with ruleorder"); + } + CallContext.current().setEventDetails("Reordering permissions for role id: " + projectRole.getId()); + result = updateProjectRolePermissionOrder(projectRole); + + } else if (getProjectRuleId() != null || getProjectRolePermission() != null ) { + if (getProjectRulePermissionOrder() != null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Parameters permission and ruleid must be mutually exclusive with ruleorder"); + } + ProjectRolePermission rolePermission = getValidProjectRolePermission(); + CallContext.current().setEventDetails("Updating project role permission for rule id: " + getProjectRuleId() + " to: " + getProjectRolePermission().toString()); + result = projRoleService.updateProjectRolePermission(projectId, projectRole, rolePermission, getProjectRolePermission()); + } + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + setResponseObject(response); + } + + private ProjectRolePermission getValidProjectRolePermission() { + ProjectRolePermission rolePermission = projRoleService.findProjectRolePermission(getProjectRuleId()); + if (rolePermission == null || rolePermission.getProjectId() != getProjectId()) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Role permission doesn't exist in the project, probably because of invalid rule id"); + } + return rolePermission; + } + + private boolean updateProjectRolePermissionOrder(ProjectRole projectRole) { + final List rolePermissionsOrder = new ArrayList<>(); + for (Long rolePermissionId : getProjectRulePermissionOrder()) { + final ProjectRolePermission rolePermission = projRoleService.findProjectRolePermission(rolePermissionId); + if (rolePermission == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Provided project role permission(s) do not exist"); + } + rolePermissionsOrder.add(rolePermission); + } + return projRoleService.updateProjectRolePermission(projectId, projectRole, rolePermissionsOrder); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java index 22deb035b518..b2e0adc38c7f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java @@ -16,6 +16,12 @@ // under the License. package org.apache.cloudstack.api.command.user.account; +import java.util.List; + +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.commons.lang3.EnumUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -31,6 +37,7 @@ import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; @APICommand(name = "addAccountToProject", description = "Adds account to a project", responseObject = SuccessResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -56,6 +63,14 @@ public class AddAccountToProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "email to which invitation to the project is going to be sent") private String email; + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, entityType = ProjectRoleResponse.class, + description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRoleId; + + @Parameter(name = ApiConstants.ROLE_TYPE, type = BaseCmd.CommandType.STRING, + description = "Project role type to be assigned to the user - Admin/Regular") + private String roleType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -72,6 +87,21 @@ public String getEmail() { return email; } + public Long getProjectRoleId() { + return projectRoleId; + } + + public ProjectAccount.Role getRoleType() { + if (roleType != null) { + String role = roleType.substring(0, 1).toUpperCase() + roleType.substring(1).toLowerCase(); + if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, role)) { + throw new InvalidParameterValueException("Only Admin or Regular project role types are valid"); + } + return Enum.valueOf(ProjectAccount.Role.class, role); + } + return null; + } + @Override public String getCommandName() { return s_name; @@ -88,7 +118,7 @@ public void execute() { } CallContext.current().setEventDetails("Project ID: " + projectId + "; accountName " + accountName); - boolean result = _projectService.addAccountToProject(getProjectId(), getAccountName(), getEmail()); + boolean result = _projectService.addAccountToProject(getProjectId(), getAccountName(), getEmail(), getProjectRoleId(), getRoleType()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); @@ -110,6 +140,11 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(getProjectId()).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(projectId); + } + @Override public String getEventType() { return EventTypes.EVENT_PROJECT_ACCOUNT_ADD; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java new file mode 100644 index 000000000000..f8d77ac46e2c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -0,0 +1,139 @@ +// 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.api.command.user.account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.EnumUtils; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.projects.ProjectAccount; + +@APICommand(name = AddUserToProjectCmd.APINAME, description = "Adds user to a project", responseObject = SuccessResponse.class, since = "4.14", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class AddUserToProjectCmd extends BaseAsyncCmd { + public static final String APINAME = "addUserToProject"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROJECT_ID, + type = BaseCmd.CommandType.UUID, + entityType = ProjectResponse.class, + required = true, + description = "ID of the project to add the user to") + private Long projectId; + + @Parameter(name = ApiConstants.USER_ID, type = BaseCmd.CommandType.UUID, required = true, entityType = UserResponse.class, + description = "User UUID, required for adding account from external provisioning system") + private Long userId; + + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) + private Long projectRoleId; + + @Parameter(name = ApiConstants.ROLE_TYPE, type = BaseCmd.CommandType.STRING, required = true, + description = "Project role type to be assigned to the user - Admin/Regular") + private String roleType; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public Long getUserId() { + return userId; + } + + public Long getProjectRoleId() { + return projectRoleId; + } + + public ProjectAccount.Role getRoleType() { + String role = roleType.substring(0,1).toUpperCase()+ roleType.substring(1).toLowerCase(); + if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, role )){ + throw new InvalidParameterValueException("Only Admin or Regular project role types are valid"); + } + return Enum.valueOf(ProjectAccount.Role.class, role); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_PROJECT_USER_ADD; + } + + @Override + public String getEventDescription() { + return "Adding user "+getUserId()+" to Project "+getProjectId(); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + validateInput(); + boolean result = _projectService.addUserToProject(getProjectId(), getUserId(), getProjectRoleId(), getRoleType()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add account to the project"); + } + + } + + private void validateInput() { + if (getProjectId() < 1L) { + throw new InvalidParameterValueException("Invalid Project ID provided"); + } + if (getUserId() < 1L) { + throw new InvalidParameterValueException("Invalid User ID provided"); + } + if (getProjectRoleId() < 1L) { + throw new InvalidParameterValueException("Invalid Project role ID provided"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java index 60003b7b8fd1..20d5b319bb52 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.account; +import java.util.List; + import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -97,6 +99,11 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(projectId).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(projectId); + } + @Override public String getEventType() { return EventTypes.EVENT_PROJECT_ACCOUNT_REMOVE; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java new file mode 100644 index 000000000000..06195b6a3e94 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java @@ -0,0 +1,117 @@ +// 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.api.command.user.account; + +import java.util.List; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.project.DeleteProjectCmd; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.projects.Project; + +@APICommand(name = DeleteUserFromProjectCmd.APINAME, description = "Deletes user from the project", responseObject = SuccessResponse.class, since = "4.15.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DeleteUserFromProjectCmd extends BaseAsyncCmd { + public static final Logger LOGGER = Logger.getLogger(DeleteProjectCmd.class.getName()); + public static final String APINAME = "deleteUserFromProject"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.PROJECT_ID, + type = BaseCmd.CommandType.UUID, + entityType = ProjectResponse.class, + required = true, + description = "ID of the project to remove the user from") + private Long projectId; + + @Parameter(name = ApiConstants.USER_ID, type = BaseCmd.CommandType.UUID, entityType = UserResponse.class, + required = true, description = "Id of the user to be removed from the project") + private Long userId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public Long getUserId() { + return userId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getEventType() { + return EventTypes.EVENT_PROJECT_USER_REMOVE; + } + + @Override + public String getEventDescription() { + return "Removing user " + userId + " from project: " + projectId; + } + + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + Project project = _projectService.getProject(projectId); + if (project == null) { + throw new InvalidParameterValueException("Unable to find project by ID " + projectId); + } + return _projectService.getProjectOwner(projectId).getId(); + } + + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(projectId); + } + + @Override + public void execute() { + CallContext.current().setEventDetails("Project ID: " + projectId + "; user ID: " + userId); + boolean result = _projectService.deleteUserFromProject(getProjectId(), getUserId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete account from the project"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java index 5b1b76e95eaa..eda814c58146 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.project; +import java.util.List; + import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -42,7 +44,7 @@ public class ActivateProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") private Long id; ///////////////////////////////////////////////////// @@ -69,6 +71,11 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(getId()).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(id); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java index 2ffa142d22d8..3ad44f1cda4a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java @@ -16,18 +16,19 @@ // under the License. package org.apache.cloudstack.api.command.user.project; -import org.apache.cloudstack.api.ApiArgValidator; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; @@ -49,9 +50,16 @@ public class CreateProjectCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "account who will be Admin for the project") private String accountName; + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, + description = "user ID of the account to be assigned as owner of the project i.e., Project Admin", since = "4.15.0") + private Long userId; + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "domain ID of the account owning a project") private Long domainId; + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account owning a project") + private Long accountId; + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, validations = ApiArgValidator.NotNullOrEmpty, description = "name of the project") private String name; @@ -73,16 +81,24 @@ public String getAccountName() { public Long getDomainId() { if (domainId != null) { return domainId; - } else { - return CallContext.current().getCallingAccount().getDomainId(); } + return CallContext.current().getCallingAccount().getDomainId(); + } public String getName() { return name; } + public Long getUserId() { + return userId; + } + + public Long getAccountId() { + return accountId; + } + public String getDisplayText() { return displayText; } @@ -126,7 +142,7 @@ public void execute() { @Override public void create() throws ResourceAllocationException { CallContext.current().setEventDetails("Project Name: " + getName()); - Project project = _projectService.createProject(getName(), getDisplayText(), getAccountName(), getDomainId()); + Project project = _projectService.createProject(getName(), getDisplayText(), getAccountName(), getDomainId(), getUserId(), getAccountId()); if (project != null) { this.setEntityId(project.getId()); this.setEntityUuid(project.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java index 3a2e48d51abf..5643d04171ff 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.project; +import java.util.List; + import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -43,7 +45,7 @@ public class DeleteProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted") + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted") private Long id; ///////////////////////////////////////////////////// @@ -96,4 +98,9 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(id).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(id); + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java index db77916d5667..4f83d0ec4947 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java @@ -48,7 +48,7 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "list projects by project ID") + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "list projects by project ID") private Long id; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "list projects by name") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java index 69a4b77c7a5f..688c8c96ecda 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.project; +import java.util.List; + import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -44,7 +46,7 @@ public class SuspendProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be suspended") + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be suspended") private Long id; ///////////////////////////////////////////////////// @@ -98,4 +100,9 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(id).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(id); + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index 11e5e35bac3d..b0f46b7260bb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.project; -import org.apache.log4j.Logger; +import java.util.List; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -24,13 +24,19 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.EnumUtils; +import org.apache.log4j.Logger; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; @APICommand(name = "updateProject", description = "Updates a project", responseObject = ProjectResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -43,7 +49,7 @@ public class UpdateProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") private Long id; @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "new Admin account for the project") @@ -52,6 +58,18 @@ public class UpdateProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "display text of the project") private String displayText; + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "ID of the user to be promoted/demoted") + private Long userId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the user to be promoted/demoted") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account owning a project") + private Long accountId; + + @Parameter(name = ApiConstants.ROLE_TYPE, type = CommandType.STRING, required = true, description = "Account level role to be assigned to the user/account : Admin/Regular") + private String roleType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +86,33 @@ public String getDisplayText() { return displayText; } + public Long getUserId() { + return userId; + } + + public Long getDomainId() { + if (domainId != null) { + return domainId; + } + return CallContext.current().getCallingAccount().getDomainId(); + } + + public ProjectAccount.Role getRoleType(String role) { + String type = role.substring(0, 1).toUpperCase() + role.substring(1).toLowerCase(); + if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, type)) { + throw new InvalidParameterValueException("Only Admin or Regular project role types are valid"); + } + return Enum.valueOf(ProjectAccount.Role.class, type); + } + + public ProjectAccount.Role getAccountRole() { + return getRoleType(roleType); + } + + public Long getAccountId() { + return accountId; + } + @Override public String getCommandName() { return s_name; @@ -84,6 +129,11 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(id).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(id); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -91,7 +141,21 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceAllocationException { CallContext.current().setEventDetails("Project id: " + getId()); - Project project = _projectService.updateProject(getId(), getDisplayText(), getAccountName()); + if (getAccountName() != null && (getUserId() != null)) { + throw new InvalidParameterValueException("Account name and user ID are mutually exclusive. Provide either account name" + + "to update account or user ID to update the user of the project"); + } + + if (getUserId() != null) { + if (getDomainId() == null) { + throw new InvalidParameterValueException("Domain ID needs to be provided when User ID is provided"); + } + if (getAccountId() == null) { + throw new InvalidParameterValueException("Account ID needs to be provided when User ID is provided"); + } + } + + Project project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountId(), getDomainId(), getAccountRole()); if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java new file mode 100644 index 000000000000..33453340bdc8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java @@ -0,0 +1,64 @@ +// 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.api.response; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.Rule; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class BaseRolePermissionResponse extends BaseResponse { + @SerializedName(ApiConstants.RULE) + @Param(description = "the api name or wildcard rule") + private String rule; + + @SerializedName(ApiConstants.PERMISSION) + @Param(description = "the permission type of the api name or wildcard rule, allow/deny") + private String rulePermission; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the role permission") + private String ruleDescription; + + public String getRule() { + return rule; + } + + public void setRule(Rule rule) { + if (rule != null) { + this.rule = rule.getRuleString(); + } + } + + public String getRulePermission() { + return rulePermission; + } + + public void setRulePermission(Permission rulePermission) { + if (rulePermission != null) { + this.rulePermission = rulePermission.name().toLowerCase(); + } + } + + public void setDescription(String description) { + this.ruleDescription = description; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java new file mode 100644 index 000000000000..339d092c1f27 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java @@ -0,0 +1,50 @@ +// 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.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class BaseRoleResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the role") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the role") + private String roleName; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the role") + private String roleDescription; + + public void setId(String id) { + this.id = id; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public void setDescription(String description) { + this.roleDescription = description; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java new file mode 100644 index 000000000000..0b0638ffcea8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java @@ -0,0 +1,77 @@ +// 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.api.response; + +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ProjectRolePermission.class) +public class ProjectRolePermissionResponse extends BaseRolePermissionResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the project role permission") + private String id; + + @SerializedName(ApiConstants.PROJECT_ROLE_ID) + @Param(description = "the ID of the project role to which the role permission belongs") + private String projectRoleId; + + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "the ID of the project") + private Long projectId; + + @SerializedName(ApiConstants.PROJECT_ROLE_NAME) + @Param(description = "the name of the project role to which the role permission belongs") + private String projectRoleName; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + + public String getProjectRoleId() { + return projectRoleId; + } + + public void setProjectRoleId(String projectRoleId) { + this.projectRoleId = projectRoleId; + } + + public String getProjectRoleName() { + return projectRoleName; + } + + public void setProjectRoleName(String projectRoleName) { + this.projectRoleName = projectRoleName; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java new file mode 100644 index 000000000000..f8ee02ac7a3b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java @@ -0,0 +1,40 @@ +// 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.api.response; + +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ProjectRole.class) +public class ProjectRoleResponse extends BaseRoleResponse { + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "the id of the project") + private Long projectId; + + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RolePermissionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RolePermissionResponse.java index ac1c5298c1e8..acedfd42552f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RolePermissionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RolePermissionResponse.java @@ -17,16 +17,15 @@ package org.apache.cloudstack.api.response; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + @EntityReference(value = RolePermission.class) -public class RolePermissionResponse extends BaseResponse { +public class RolePermissionResponse extends BaseRolePermissionResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the role permission") private String id; @@ -39,18 +38,6 @@ public class RolePermissionResponse extends BaseResponse { @Param(description = "the name of the role to which the role permission belongs") private String roleName; - @SerializedName(ApiConstants.RULE) - @Param(description = "the api name or wildcard rule") - private String rule; - - @SerializedName(ApiConstants.PERMISSION) - @Param(description = "the permission type of the api name or wildcard rule, allow/deny") - private String rulePermission; - - @SerializedName(ApiConstants.DESCRIPTION) - @Param(description = "the description of the role permission") - private String ruleDescription; - public String getId() { return id; } @@ -74,28 +61,4 @@ public String getRoleName() { public void setRoleName(String roleName) { this.roleName = roleName; } - - public String getRule() { - return rule; - } - - public void setRule(Rule rule) { - if (rule != null) { - this.rule = rule.getRuleString(); - } - } - - public String getRulePermission() { - return rulePermission; - } - - public void setRulePermission(RolePermission.Permission rulePermission) { - if (rulePermission != null) { - this.rulePermission = rulePermission.name().toLowerCase(); - } - } - - public void setDescription(String description) { - this.ruleDescription = description; - } } \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java index fd4bf28e6184..f82f9e2aeaa8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java @@ -17,47 +17,25 @@ package org.apache.cloudstack.api.response; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -@EntityReference(value = Role.class) -public class RoleResponse extends BaseResponse { - @SerializedName(ApiConstants.ID) - @Param(description = "the ID of the role") - private String id; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; - @SerializedName(ApiConstants.NAME) - @Param(description = "the name of the role") - private String roleName; +@EntityReference(value = Role.class) +public class RoleResponse extends BaseRoleResponse { @SerializedName(ApiConstants.TYPE) @Param(description = "the type of the role") private String roleType; - @SerializedName(ApiConstants.DESCRIPTION) - @Param(description = "the description of the role") - private String roleDescription; - - public void setId(String id) { - this.id = id; - } - - public void setRoleName(String roleName) { - this.roleName = roleName; - } - public void setRoleType(RoleType roleType) { if (roleType != null) { this.roleType = roleType.name(); } } - public void setDescription(String description) { - this.roleDescription = description; - } } diff --git a/client/pom.xml b/client/pom.xml index bd58b05cdd2e..d071e02ecc2e 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT @@ -102,6 +102,11 @@ cloud-plugin-acl-dynamic-role-based ${project.version} + + org.apache.cloudstack + cloud-plugin-acl-project-role-based + ${project.version} + org.apache.cloudstack cloud-plugin-ca-rootca diff --git a/core/pom.xml b/core/pom.xml index 809c69709930..ff63e50c4a0a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/developer/pom.xml b/developer/pom.xml index e45f6650934b..6f983ce2bbe3 100644 --- a/developer/pom.xml +++ b/developer/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/engine/api/pom.xml b/engine/api/pom.xml index f5550438850a..3292ef5cab9d 100644 --- a/engine/api/pom.xml +++ b/engine/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/components-api/pom.xml b/engine/components-api/pom.xml index e57681568a63..5b809ead2b6c 100644 --- a/engine/components-api/pom.xml +++ b/engine/components-api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/network/pom.xml b/engine/network/pom.xml index 906c3b1ea09e..fcf89c88a6df 100644 --- a/engine/network/pom.xml +++ b/engine/network/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml index d981e72f80bd..404bd2afbab5 100755 --- a/engine/orchestration/pom.xml +++ b/engine/orchestration/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/pom.xml b/engine/pom.xml index 475352664a73..f360f3a42bc7 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index 9c097fba12e3..ef15339dc5ce 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java index 8a337d620d2a..5a78c5a69eb6 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java @@ -46,6 +46,9 @@ public class ProjectAccountVO implements ProjectAccount, InternalIdentity { @Column(name = "account_id") private long accountId; + @Column(name="user_id") + private Long userId; + @Column(name = "account_role") @Enumerated(value = EnumType.STRING) private Role accountRole = Role.Regular; @@ -53,17 +56,22 @@ public class ProjectAccountVO implements ProjectAccount, InternalIdentity { @Column(name = "project_account_id") long projectAccountId; + @Column(name = "project_role_id") + private Long projectRoleId; + @Column(name = GenericDao.CREATED_COLUMN) private Date created; protected ProjectAccountVO() { } - public ProjectAccountVO(Project project, long accountId, Role accountRole) { + public ProjectAccountVO(Project project, long accountId, Role accountRole, Long userId, Long projectRoleId) { this.accountId = accountId; this.accountRole = accountRole; this.projectId = project.getId(); this.projectAccountId = project.getProjectAccountId(); + this.userId = userId; + this.projectRoleId = projectRoleId; } @Override @@ -81,6 +89,13 @@ public long getAccountId() { return accountId; } + @Override + public Long getUserId() { return userId; } + + public void setUserId(Long userId) { + this.userId = userId; + } + @Override public Role getAccountRole() { return accountRole; @@ -91,6 +106,13 @@ public long getProjectAccountId() { return projectAccountId; } + public void setProjectRoleId(Long projectRoleId) { + this.projectRoleId = projectRoleId; + } + + @Override + public Long getProjectRoleId() { return projectRoleId; } + public void setAccountRole(Role accountRole) { this.accountRole = accountRole; } diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java index 8a251a6527a1..0e85bb7fbd90 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java @@ -25,10 +25,14 @@ public interface ProjectAccountDao extends GenericDao { ProjectAccountVO getProjectOwner(long projectId); + List getProjectOwners(long projectId); + List listByProjectId(long projectId); ProjectAccountVO findByProjectIdAccountId(long projectId, long accountId); + ProjectAccountVO findByProjectIdUserId(long projectId, long accountId, long userId); + boolean canAccessProjectAccount(long accountId, long projectAccountId); boolean canModifyProjectAccount(long accountId, long projectAccountId); @@ -40,4 +44,8 @@ public interface ProjectAccountDao extends GenericDao { Long countByAccountIdAndRole(long accountId, ProjectAccount.Role role); void removeAccountFromProjects(long accountId); + + boolean canUserModifyProject(long projectId, long accountId, long userId); + + List listUsersOrAccountsByRole(long id); } diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java index ecf1aab6f4f2..064b9adfb86d 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java @@ -18,7 +18,6 @@ import java.util.List; - import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -37,6 +36,7 @@ public class ProjectAccountDaoImpl extends GenericDaoBase AdminSearch; final GenericSearchBuilder ProjectAccountSearch; final GenericSearchBuilder CountByRoleSearch; + public static final Logger s_logger = Logger.getLogger(ProjectAccountDaoImpl.class.getName()); protected ProjectAccountDaoImpl() { @@ -45,6 +45,8 @@ protected ProjectAccountDaoImpl() { AllFieldsSearch.and("projectId", AllFieldsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AllFieldsSearch.and("projectAccountId", AllFieldsSearch.entity().getProjectAccountId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("userId", AllFieldsSearch.entity().getUserId(), Op.EQ); + AllFieldsSearch.and("projectRoleId", AllFieldsSearch.entity().getProjectRoleId(), Op.EQ); AllFieldsSearch.done(); AdminSearch = createSearchBuilder(Long.class); @@ -74,6 +76,14 @@ public ProjectAccountVO getProjectOwner(long projectId) { return findOneBy(sc); } + public List getProjectOwners(long projectId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("role", ProjectAccount.Role.Admin); + sc.setParameters("projectId", projectId); + + return listBy(sc); + } + @Override public List listByProjectId(long projectId) { SearchCriteria sc = AllFieldsSearch.create(); @@ -91,6 +101,16 @@ public ProjectAccountVO findByProjectIdAccountId(long projectId, long accountId) return findOneBy(sc); } + @Override + public ProjectAccountVO findByProjectIdUserId(long projectId, long accountId, long userId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("projectId", projectId); + sc.setParameters("userId", userId); + sc.setParameters("accountId", accountId); + + return findOneBy(sc); + } + @Override public boolean canAccessProjectAccount(long accountId, long projectAccountId) { SearchCriteria sc = AllFieldsSearch.create(); @@ -152,4 +172,23 @@ public void removeAccountFromProjects(long accountId) { } } + @Override + public boolean canUserModifyProject(long projectId, long accountId, long userId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("role", ProjectAccount.Role.Admin); + sc.setParameters("projectId",projectId); + sc.setParameters("accountId", accountId); + sc.setParameters("userId", userId); + if (findOneBy(sc) != null) { + return true; + } + return false; + } + + @Override + public List listUsersOrAccountsByRole(long id) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("projectRoleId", id); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java index eed2e2fd449c..7db4e4cd9d38 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java @@ -68,6 +68,7 @@ import com.cloud.upgrade.dao.Upgrade41200to41300; import com.cloud.upgrade.dao.Upgrade41300to41310; import com.cloud.upgrade.dao.Upgrade41310to41400; +import com.cloud.upgrade.dao.Upgrade41400to41500; import com.cloud.upgrade.dao.Upgrade420to421; import com.cloud.upgrade.dao.Upgrade421to430; import com.cloud.upgrade.dao.Upgrade430to440; @@ -191,6 +192,7 @@ public DatabaseUpgradeChecker() { .next("4.12.0.0", new Upgrade41200to41300()) .next("4.13.0.0", new Upgrade41300to41310()) .next("4.13.1.0", new Upgrade41310to41400()) + .next("4.14.0.0", new Upgrade41400to41500()) .build(); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41310to41400.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41310to41400.java index 0ce6809bc64e..f1a333e7d48b 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41310to41400.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41310to41400.java @@ -19,17 +19,9 @@ import java.io.InputStream; import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import org.apache.log4j.Logger; -import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.exception.CloudRuntimeException; public class Upgrade41310to41400 implements DbUpgrade { @@ -64,175 +56,6 @@ public InputStream[] getPrepareScripts() { @Override public void performDataMigration(Connection conn) { - updateSystemVmTemplates(conn); - } - - @SuppressWarnings("serial") - private void updateSystemVmTemplates(final Connection conn) { - LOG.debug("Updating System Vm template IDs"); - final Set hypervisorsListInUse = new HashSet(); - try (PreparedStatement pstmt = conn.prepareStatement("select distinct(hypervisor_type) from `cloud`.`cluster` where removed is null"); ResultSet rs = pstmt.executeQuery()) { - while (rs.next()) { - switch (Hypervisor.HypervisorType.getType(rs.getString(1))) { - case XenServer: - hypervisorsListInUse.add(Hypervisor.HypervisorType.XenServer); - break; - case KVM: - hypervisorsListInUse.add(Hypervisor.HypervisorType.KVM); - break; - case VMware: - hypervisorsListInUse.add(Hypervisor.HypervisorType.VMware); - break; - case Hyperv: - hypervisorsListInUse.add(Hypervisor.HypervisorType.Hyperv); - break; - case LXC: - hypervisorsListInUse.add(Hypervisor.HypervisorType.LXC); - break; - case Ovm3: - hypervisorsListInUse.add(Hypervisor.HypervisorType.Ovm3); - break; - default: - break; - } - } - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates: Exception caught while getting hypervisor types from clusters: " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while getting hypervisor types from clusters", e); - } - - final Map NewTemplateNameList = new HashMap() { - { - put(Hypervisor.HypervisorType.KVM, "systemvm-kvm-4.14.0"); - put(Hypervisor.HypervisorType.VMware, "systemvm-vmware-4.14.0"); - put(Hypervisor.HypervisorType.XenServer, "systemvm-xenserver-4.14.0"); - put(Hypervisor.HypervisorType.Hyperv, "systemvm-hyperv-4.14.0"); - put(Hypervisor.HypervisorType.LXC, "systemvm-lxc-4.14.0"); - put(Hypervisor.HypervisorType.Ovm3, "systemvm-ovm3-4.14.0"); - } - }; - - final Map routerTemplateConfigurationNames = new HashMap() { - { - put(Hypervisor.HypervisorType.KVM, "router.template.kvm"); - put(Hypervisor.HypervisorType.VMware, "router.template.vmware"); - put(Hypervisor.HypervisorType.XenServer, "router.template.xenserver"); - put(Hypervisor.HypervisorType.Hyperv, "router.template.hyperv"); - put(Hypervisor.HypervisorType.LXC, "router.template.lxc"); - put(Hypervisor.HypervisorType.Ovm3, "router.template.ovm3"); - } - }; - - final Map newTemplateUrl = new HashMap() { - { - put(Hypervisor.HypervisorType.KVM, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-kvm.qcow2.bz2"); - put(Hypervisor.HypervisorType.VMware, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-vmware.ova"); - put(Hypervisor.HypervisorType.XenServer, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-xen.vhd.bz2"); - put(Hypervisor.HypervisorType.Hyperv, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-hyperv.vhd.zip"); - put(Hypervisor.HypervisorType.LXC, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-kvm.qcow2.bz2"); - put(Hypervisor.HypervisorType.Ovm3, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-ovm.raw.bz2"); - } - }; - - final Map newTemplateChecksum = new HashMap() { - { - put(Hypervisor.HypervisorType.KVM, "d15ed159be32151b07e3211caf9cb802"); - put(Hypervisor.HypervisorType.XenServer, "fcaf1abc9aa62e7ed75f62b3092a01a2"); - put(Hypervisor.HypervisorType.VMware, "eb39f8b5a556dfc93c6be23ae45f34e1"); - put(Hypervisor.HypervisorType.Hyperv, "b4e91c14958e0fca9470695b0be05f99"); - put(Hypervisor.HypervisorType.LXC, "d15ed159be32151b07e3211caf9cb802"); - put(Hypervisor.HypervisorType.Ovm3, "1f97f4beb30af8cda886f1e977514704"); - } - }; - - for (final Map.Entry hypervisorAndTemplateName : NewTemplateNameList.entrySet()) { - LOG.debug("Updating " + hypervisorAndTemplateName.getKey() + " System Vms"); - try (PreparedStatement pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = ? and removed is null order by id desc limit 1")) { - // Get 4.11 systemvm template id for corresponding hypervisor - long templateId = -1; - pstmt.setString(1, hypervisorAndTemplateName.getValue()); - try (ResultSet rs = pstmt.executeQuery()) { - if (rs.next()) { - templateId = rs.getLong(1); - } - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates: Exception caught while getting ids of templates: " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates: Exception caught while getting ids of templates", e); - } - - // change template type to SYSTEM - if (templateId != -1) { - try (PreparedStatement templ_type_pstmt = conn.prepareStatement("update `cloud`.`vm_template` set type='SYSTEM' where id = ?");) { - templ_type_pstmt.setLong(1, templateId); - templ_type_pstmt.executeUpdate(); - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates:Exception while updating template with id " + templateId + " to be marked as 'system': " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while updating template with id " + templateId + " to be marked as 'system'", e); - } - // update template ID of system Vms - try (PreparedStatement update_templ_id_pstmt = conn - .prepareStatement("update `cloud`.`vm_instance` set vm_template_id = ? where type <> 'User' and hypervisor_type = ? and removed is NULL");) { - update_templ_id_pstmt.setLong(1, templateId); - update_templ_id_pstmt.setString(2, hypervisorAndTemplateName.getKey().toString()); - update_templ_id_pstmt.executeUpdate(); - } catch (final Exception e) { - LOG.error("updateSystemVmTemplates:Exception while setting template for " + hypervisorAndTemplateName.getKey().toString() + " to " + templateId - + ": " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting template for " + hypervisorAndTemplateName.getKey().toString() + " to " - + templateId, e); - } - - // Change value of global configuration parameter - // router.template.* for the corresponding hypervisor - try (PreparedStatement update_pstmt = conn.prepareStatement("UPDATE `cloud`.`configuration` SET value = ? WHERE name = ?");) { - update_pstmt.setString(1, hypervisorAndTemplateName.getValue()); - update_pstmt.setString(2, routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey())); - update_pstmt.executeUpdate(); - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates:Exception while setting " + routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey()) + " to " - + hypervisorAndTemplateName.getValue() + ": " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting " - + routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey()) + " to " + hypervisorAndTemplateName.getValue(), e); - } - - // Change value of global configuration parameter - // minreq.sysvmtemplate.version for the ACS version - try (PreparedStatement update_pstmt = conn.prepareStatement("UPDATE `cloud`.`configuration` SET value = ? WHERE name = ?");) { - update_pstmt.setString(1, "4.14.0"); - update_pstmt.setString(2, "minreq.sysvmtemplate.version"); - update_pstmt.executeUpdate(); - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates:Exception while setting 'minreq.sysvmtemplate.version' to 4.14.0: " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting 'minreq.sysvmtemplate.version' to 4.14.0", e); - } - } else { - if (hypervisorsListInUse.contains(hypervisorAndTemplateName.getKey())) { - throw new CloudRuntimeException(getUpgradedVersion() + hypervisorAndTemplateName.getKey() + " SystemVm template not found. Cannot upgrade system Vms"); - } else { - LOG.warn(getUpgradedVersion() + hypervisorAndTemplateName.getKey() + " SystemVm template not found. " + hypervisorAndTemplateName.getKey() - + " hypervisor is not used, so not failing upgrade"); - // Update the latest template URLs for corresponding - // hypervisor - try (PreparedStatement update_templ_url_pstmt = conn - .prepareStatement("UPDATE `cloud`.`vm_template` SET url = ? , checksum = ? WHERE hypervisor_type = ? AND type = 'SYSTEM' AND removed is null order by id desc limit 1");) { - update_templ_url_pstmt.setString(1, newTemplateUrl.get(hypervisorAndTemplateName.getKey())); - update_templ_url_pstmt.setString(2, newTemplateChecksum.get(hypervisorAndTemplateName.getKey())); - update_templ_url_pstmt.setString(3, hypervisorAndTemplateName.getKey().toString()); - update_templ_url_pstmt.executeUpdate(); - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type " - + hypervisorAndTemplateName.getKey().toString() + ": " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type " - + hypervisorAndTemplateName.getKey().toString(), e); - } - } - } - } catch (final SQLException e) { - LOG.error("updateSystemVmTemplates:Exception while getting ids of templates: " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while getting ids of templates", e); - } - } - LOG.debug("Updating System Vm Template IDs Complete"); } @Override diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java new file mode 100644 index 000000000000..e88bf6b81452 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java @@ -0,0 +1,248 @@ +// 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.upgrade.dao; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.utils.exception.CloudRuntimeException; + +public class Upgrade41400to41500 implements DbUpgrade { + + final static Logger LOG = Logger.getLogger(Upgrade41400to41500.class); + + @Override + public String[] getUpgradableVersionRange() { + return new String[] {"4.14.0.0", "4.15.0.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.15.0.0"; + } + + @Override + public boolean supportsRollingUpgrade() { + return false; + } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-41400to41500.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } + + @Override + public void performDataMigration(Connection conn) { + updateSystemVmTemplates(conn); + } + + @SuppressWarnings("serial") + private void updateSystemVmTemplates(final Connection conn) { + LOG.debug("Updating System Vm template IDs"); + final Set hypervisorsListInUse = new HashSet(); + try (PreparedStatement pstmt = conn.prepareStatement("select distinct(hypervisor_type) from `cloud`.`cluster` where removed is null"); ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + switch (Hypervisor.HypervisorType.getType(rs.getString(1))) { + case XenServer: + hypervisorsListInUse.add(Hypervisor.HypervisorType.XenServer); + break; + case KVM: + hypervisorsListInUse.add(Hypervisor.HypervisorType.KVM); + break; + case VMware: + hypervisorsListInUse.add(Hypervisor.HypervisorType.VMware); + break; + case Hyperv: + hypervisorsListInUse.add(Hypervisor.HypervisorType.Hyperv); + break; + case LXC: + hypervisorsListInUse.add(Hypervisor.HypervisorType.LXC); + break; + case Ovm3: + hypervisorsListInUse.add(Hypervisor.HypervisorType.Ovm3); + break; + default: + break; + } + } + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates: Exception caught while getting hypervisor types from clusters: " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while getting hypervisor types from clusters", e); + } + + final Map NewTemplateNameList = new HashMap() { + { + put(Hypervisor.HypervisorType.KVM, "systemvm-kvm-4.14.0"); + put(Hypervisor.HypervisorType.VMware, "systemvm-vmware-4.14.0"); + put(Hypervisor.HypervisorType.XenServer, "systemvm-xenserver-4.14.0"); + put(Hypervisor.HypervisorType.Hyperv, "systemvm-hyperv-4.14.0"); + put(Hypervisor.HypervisorType.LXC, "systemvm-lxc-4.14.0"); + put(Hypervisor.HypervisorType.Ovm3, "systemvm-ovm3-4.14.0"); + } + }; + + final Map routerTemplateConfigurationNames = new HashMap() { + { + put(Hypervisor.HypervisorType.KVM, "router.template.kvm"); + put(Hypervisor.HypervisorType.VMware, "router.template.vmware"); + put(Hypervisor.HypervisorType.XenServer, "router.template.xenserver"); + put(Hypervisor.HypervisorType.Hyperv, "router.template.hyperv"); + put(Hypervisor.HypervisorType.LXC, "router.template.lxc"); + put(Hypervisor.HypervisorType.Ovm3, "router.template.ovm3"); + } + }; + + final Map newTemplateUrl = new HashMap() { + { + put(Hypervisor.HypervisorType.KVM, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-kvm.qcow2.bz2"); + put(Hypervisor.HypervisorType.VMware, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-vmware.ova"); + put(Hypervisor.HypervisorType.XenServer, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-xen.vhd.bz2"); + put(Hypervisor.HypervisorType.Hyperv, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-hyperv.vhd.zip"); + put(Hypervisor.HypervisorType.LXC, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-kvm.qcow2.bz2"); + put(Hypervisor.HypervisorType.Ovm3, "https://download.cloudstack.org/systemvm/4.14/systemvmtemplate-4.14.0-ovm.raw.bz2"); + } + }; + + final Map newTemplateChecksum = new HashMap() { + { + put(Hypervisor.HypervisorType.KVM, "d15ed159be32151b07e3211caf9cb802"); + put(Hypervisor.HypervisorType.XenServer, "fcaf1abc9aa62e7ed75f62b3092a01a2"); + put(Hypervisor.HypervisorType.VMware, "eb39f8b5a556dfc93c6be23ae45f34e1"); + put(Hypervisor.HypervisorType.Hyperv, "b4e91c14958e0fca9470695b0be05f99"); + put(Hypervisor.HypervisorType.LXC, "d15ed159be32151b07e3211caf9cb802"); + put(Hypervisor.HypervisorType.Ovm3, "1f97f4beb30af8cda886f1e977514704"); + } + }; + + for (final Map.Entry hypervisorAndTemplateName : NewTemplateNameList.entrySet()) { + LOG.debug("Updating " + hypervisorAndTemplateName.getKey() + " System Vms"); + try (PreparedStatement pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = ? and removed is null order by id desc limit 1")) { + // Get 4.11 systemvm template id for corresponding hypervisor + long templateId = -1; + pstmt.setString(1, hypervisorAndTemplateName.getValue()); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + templateId = rs.getLong(1); + } + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates: Exception caught while getting ids of templates: " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates: Exception caught while getting ids of templates", e); + } + + // change template type to SYSTEM + if (templateId != -1) { + try (PreparedStatement templ_type_pstmt = conn.prepareStatement("update `cloud`.`vm_template` set type='SYSTEM' where id = ?");) { + templ_type_pstmt.setLong(1, templateId); + templ_type_pstmt.executeUpdate(); + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates:Exception while updating template with id " + templateId + " to be marked as 'system': " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while updating template with id " + templateId + " to be marked as 'system'", e); + } + // update template ID of system Vms + try (PreparedStatement update_templ_id_pstmt = conn + .prepareStatement("update `cloud`.`vm_instance` set vm_template_id = ? where type <> 'User' and hypervisor_type = ? and removed is NULL");) { + update_templ_id_pstmt.setLong(1, templateId); + update_templ_id_pstmt.setString(2, hypervisorAndTemplateName.getKey().toString()); + update_templ_id_pstmt.executeUpdate(); + } catch (final Exception e) { + LOG.error("updateSystemVmTemplates:Exception while setting template for " + hypervisorAndTemplateName.getKey().toString() + " to " + templateId + + ": " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting template for " + hypervisorAndTemplateName.getKey().toString() + " to " + + templateId, e); + } + + // Change value of global configuration parameter + // router.template.* for the corresponding hypervisor + try (PreparedStatement update_pstmt = conn.prepareStatement("UPDATE `cloud`.`configuration` SET value = ? WHERE name = ?");) { + update_pstmt.setString(1, hypervisorAndTemplateName.getValue()); + update_pstmt.setString(2, routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey())); + update_pstmt.executeUpdate(); + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates:Exception while setting " + routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey()) + " to " + + hypervisorAndTemplateName.getValue() + ": " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting " + + routerTemplateConfigurationNames.get(hypervisorAndTemplateName.getKey()) + " to " + hypervisorAndTemplateName.getValue(), e); + } + + // Change value of global configuration parameter + // minreq.sysvmtemplate.version for the ACS version + try (PreparedStatement update_pstmt = conn.prepareStatement("UPDATE `cloud`.`configuration` SET value = ? WHERE name = ?");) { + update_pstmt.setString(1, "4.14.0"); + update_pstmt.setString(2, "minreq.sysvmtemplate.version"); + update_pstmt.executeUpdate(); + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates:Exception while setting 'minreq.sysvmtemplate.version' to 4.14.0: " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while setting 'minreq.sysvmtemplate.version' to 4.14.0", e); + } + } else { + if (hypervisorsListInUse.contains(hypervisorAndTemplateName.getKey())) { + throw new CloudRuntimeException(getUpgradedVersion() + hypervisorAndTemplateName.getKey() + " SystemVm template not found. Cannot upgrade system Vms"); + } else { + LOG.warn(getUpgradedVersion() + hypervisorAndTemplateName.getKey() + " SystemVm template not found. " + hypervisorAndTemplateName.getKey() + + " hypervisor is not used, so not failing upgrade"); + // Update the latest template URLs for corresponding + // hypervisor + try (PreparedStatement update_templ_url_pstmt = conn + .prepareStatement("UPDATE `cloud`.`vm_template` SET url = ? , checksum = ? WHERE hypervisor_type = ? AND type = 'SYSTEM' AND removed is null order by id desc limit 1");) { + update_templ_url_pstmt.setString(1, newTemplateUrl.get(hypervisorAndTemplateName.getKey())); + update_templ_url_pstmt.setString(2, newTemplateChecksum.get(hypervisorAndTemplateName.getKey())); + update_templ_url_pstmt.setString(3, hypervisorAndTemplateName.getKey().toString()); + update_templ_url_pstmt.executeUpdate(); + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type " + + hypervisorAndTemplateName.getKey().toString() + ": " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type " + + hypervisorAndTemplateName.getKey().toString(), e); + } + } + } + } catch (final SQLException e) { + LOG.error("updateSystemVmTemplates:Exception while getting ids of templates: " + e.getMessage()); + throw new CloudRuntimeException("updateSystemVmTemplates:Exception while getting ids of templates", e); + } + } + LOG.debug("Updating System Vm Template IDs Complete"); + } + + @Override + public InputStream[] getCleanupScripts() { + final String scriptFile = "META-INF/db/schema-41400to41500-cleanup.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java new file mode 100644 index 000000000000..54426308a71e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java @@ -0,0 +1,66 @@ +// 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.acl; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "project_role_permissions") +public class ProjectRolePermissionVO extends RolePermissionBaseVO implements ProjectRolePermission { + + @Column(name = "project_id") + private long projectId; + + @Column(name = "project_role_id") + private long projectRoleId; + + @Column(name = "sort_order") + private long sortOrder = 0; + + public ProjectRolePermissionVO() { super(); } + + public ProjectRolePermissionVO(final long projectId, final long projectRoleId, final String rule, final Permission permission, final String description) { + super(rule, permission, description); + this.projectId = projectId; + this.projectRoleId = projectRoleId; + } + + @Override + public long getProjectRoleId() { return projectRoleId; } + + public void setProjectRoleId(long projectRoleId) { this.projectRoleId = projectRoleId; } + + @Override + public long getProjectId() { + return projectId; + } + + public void setProjectId(long projectId) { + this.projectId = projectId; + } + + public long getSortOrder() { + return sortOrder; + } + + public void setSortOrder(long sortOrder) { + this.sortOrder = sortOrder; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java new file mode 100644 index 000000000000..6ab2e7d7e235 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java @@ -0,0 +1,100 @@ +// 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.acl; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import com.cloud.utils.db.GenericDao; + +@Entity +@Table(name = "project_role") +@SuppressWarnings("unused") +public class ProjectRoleVO implements ProjectRole { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "project_id") + private Long projectId; + + @Column(name = GenericDao.REMOVED_COLUMN) + private Date removed; + + public ProjectRoleVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public ProjectRoleVO(final String name, final String description, final Long projectId) { + this(); + this.name = name; + this.description = description; + this.projectId = projectId; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public long getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java new file mode 100644 index 000000000000..f3347ab66305 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java @@ -0,0 +1,96 @@ +// 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.acl; + + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@MappedSuperclass +public class RolePermissionBaseVO implements RolePermissionEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "rule") + private String rule; + + @Column(name = "permission", nullable = false) + @Enumerated(value = EnumType.STRING) + private Permission permission = Permission.DENY; + + @Column(name = "description") + private String description; + + public RolePermissionBaseVO() { this.uuid = UUID.randomUUID().toString(); } + + public RolePermissionBaseVO(final String rule, final Permission permission, final String description) { + this(); + this.rule = rule; + this.permission = permission; + this.description = description; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public Rule getRule() { + return new Rule(rule); + } + + public void setRule(String rule) { + this.rule = rule; + } + + @Override + public Permission getPermission() { + return permission; + } + + public void setPermission(Permission permission) { + this.permission = permission; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java index a81cebb70d75..dc5ce209319a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java @@ -19,61 +19,23 @@ import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; import javax.persistence.Table; -import java.util.UUID; @Entity @Table(name = "role_permissions") -public class RolePermissionVO implements RolePermission { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private long id; - - @Column(name = "uuid") - private String uuid; +public class RolePermissionVO extends RolePermissionBaseVO implements RolePermission { @Column(name = "role_id") private long roleId; - @Column(name = "rule") - private String rule; - - @Column(name = "permission", nullable = false) - @Enumerated(value = EnumType.STRING) - private Permission permission = RolePermission.Permission.DENY; - - @Column(name = "description") - private String description; - @Column(name = "sort_order") private long sortOrder = 0; - public RolePermissionVO() { - this.uuid = UUID.randomUUID().toString(); - } + public RolePermissionVO() { super(); } - public RolePermissionVO(final long roleId, final String rule, final Permission permission, final String description) { - this(); + public RolePermissionVO(final Long roleId, final String rule, final Permission permission, final String description) { + super(rule, permission, description); this.roleId = roleId; - this.rule = rule; - this.permission = permission; - this.description = description; - } - - @Override - public long getId() { - return id; - } - - @Override - public String getUuid() { - return uuid; } public long getRoleId() { @@ -84,32 +46,6 @@ public void setRoleId(long roleId) { this.roleId = roleId; } - @Override - public Rule getRule() { - return new Rule(rule); - } - - public void setRule(String rule) { - this.rule = rule; - } - - @Override - public Permission getPermission() { - return permission; - } - - public void setPermission(Permission permission) { - this.permission = permission; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - public long getSortOrder() { return sortOrder; } @@ -117,4 +53,5 @@ public long getSortOrder() { public void setSortOrder(long sortOrder) { this.sortOrder = sortOrder; } + } \ No newline at end of file diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDao.java new file mode 100644 index 000000000000..89f93f6bf48d --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDao.java @@ -0,0 +1,29 @@ +// 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.acl.dao; + +import java.util.List; + +import org.apache.cloudstack.acl.ProjectRoleVO; + +import com.cloud.utils.db.GenericDao; + +public interface ProjectRoleDao extends GenericDao { + List findByName(String name, Long projectId); + List findAllRoles(Long projectId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java new file mode 100644 index 000000000000..9322503e1a45 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java @@ -0,0 +1,58 @@ +// 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.acl.dao; + +import java.util.List; + +import org.apache.cloudstack.acl.ProjectRoleVO; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +public class ProjectRoleDaoImpl extends GenericDaoBase implements ProjectRoleDao{ + private final SearchBuilder ProjectRoleSearch; + + public ProjectRoleDaoImpl() { + super(); + + ProjectRoleSearch = createSearchBuilder(); + ProjectRoleSearch.and("name", ProjectRoleSearch.entity().getName(), SearchCriteria.Op.LIKE); + ProjectRoleSearch.and("project_id", ProjectRoleSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectRoleSearch.done(); + + } + @Override + public List findByName(String name, Long projectId) { + SearchCriteria sc = ProjectRoleSearch.create(); + sc.setParameters("name", "%" + name + "%"); + if (projectId != null) { + sc.setParameters("project_id", projectId); + } + return listBy(sc); + } + + @Override + public List findAllRoles(Long projectId) { + SearchCriteria sc = ProjectRoleSearch.create(); + if (projectId != null) { + sc.setParameters("project_id", projectId); + } + return listBy(sc); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java new file mode 100644 index 000000000000..115d0534d5dd --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java @@ -0,0 +1,34 @@ +// 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.acl.dao; + +import java.util.List; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.ProjectRolePermissionVO; + +import com.cloud.utils.db.GenericDao; + +public interface ProjectRolePermissionsDao extends GenericDao { + List findAllByRoleIdSorted(Long roleId, Long projectId); + boolean update(final ProjectRole role, final Long projectId, final List newOrder); + boolean update(final ProjectRole role, ProjectRolePermission rolePermission, Permission permission); + ProjectRolePermissionVO persist(final ProjectRolePermissionVO projectRolePermissionVO); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java new file mode 100644 index 000000000000..b22789884bd5 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java @@ -0,0 +1,146 @@ +// 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.acl.dao; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.ProjectRolePermissionVO; + +import com.cloud.utils.db.Attribute; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.db.UpdateBuilder; +import com.cloud.utils.exception.CloudRuntimeException; + +public class ProjectRolePermissionsDaoImpl extends GenericDaoBase implements ProjectRolePermissionsDao{ + + private final SearchBuilder ProjectRolePermissionsSearch; + private Attribute sortOrderAttribute; + + public ProjectRolePermissionsDaoImpl() { + super(); + + ProjectRolePermissionsSearch = createSearchBuilder(); + ProjectRolePermissionsSearch.and("uuid", ProjectRolePermissionsSearch.entity().getUuid(), SearchCriteria.Op.EQ); + ProjectRolePermissionsSearch.and("projectRoleId", ProjectRolePermissionsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectRolePermissionsSearch.and("projectId", ProjectRolePermissionsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectRolePermissionsSearch.and("sortOrder", ProjectRolePermissionsSearch.entity().getSortOrder(), SearchCriteria.Op.EQ); + ProjectRolePermissionsSearch.done(); + + sortOrderAttribute = _allAttributes.get("sortOrder"); + + assert (sortOrderAttribute != null) : "Couldn't find one of these attributes"; + } + + @Override + public List findAllByRoleIdSorted(Long roleId, Long projectId) { + final SearchCriteria sc = ProjectRolePermissionsSearch.create(); + if (roleId != null && roleId > 0L) { + sc.setParameters("roleId", roleId); + } + if (projectId != null && projectId > 0L) { + sc.setParameters("projectId", projectId); + } + final Filter searchBySorted = new Filter(ProjectRolePermissionVO.class, "sortOrder", true, null, null); + final List projectRolePermissionList = listBy(sc, searchBySorted); + if (projectRolePermissionList == null) { + return Collections.emptyList(); + } + return projectRolePermissionList; + } + + @Override + public boolean update(ProjectRole role, Long projectId, List newOrder) { + if (role == null || newOrder == null || newOrder.isEmpty()) { + return false; + } + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + final String failMsg = "Project role's rule permissions list has changed while you were making updates, aborted re-ordering of rules. Please try again."; + final List currentOrder = findAllByRoleIdSorted(role.getId(), projectId); + if (role.getId() < 1L || newOrder.size() != currentOrder.size()) { + throw new CloudRuntimeException(failMsg); + } + Set newOrderSet = new HashSet<>(); + newOrderSet = newOrder.stream().map(perm -> perm.getId()).collect(Collectors.toSet()); + + Set currOrderSet = new HashSet<>(); + currOrderSet = currentOrder.stream().map(perm -> perm.getId()).collect(Collectors.toSet()); + + long sortOrder = 0L; + if (!newOrderSet.equals(currOrderSet)) { + throw new CloudRuntimeException(failMsg); + } + for (ProjectRolePermission projectRolePermission : newOrder) { + final SearchCriteria sc = ProjectRolePermissionsSearch.create(); + sc.setParameters("uuid", projectRolePermission.getUuid()); + sc.setParameters("projectRoleId", role.getId()); + sc.setParameters("projectId", role.getProjectId()); + sc.setParameters("sortOrder", projectRolePermission.getSortOrder()); + + final UpdateBuilder ub = getUpdateBuilder(projectRolePermission); + ub.set(projectRolePermission, sortOrderAttribute, sortOrder); + final int result = update(ub, sc, null); + if (result < 1) { + throw new CloudRuntimeException(failMsg); + } + sortOrder++; + } + return true; + } + }); + } + + @Override + public boolean update(ProjectRole role, ProjectRolePermission rolePermission, Permission permission) { + if (role == null || rolePermission == null || permission == null) { + return false; + } + ProjectRolePermissionVO projectRolePermissionVO = findById(rolePermission.getId()); + if (projectRolePermissionVO == null) { + return false; + } + projectRolePermissionVO.setPermission(permission); + update(rolePermission.getId(), projectRolePermissionVO); + return false; + } + + @Override + public ProjectRolePermissionVO persist(final ProjectRolePermissionVO item) { + item.setSortOrder(0); + final List permissionsList = findAllByRoleIdSorted(item.getProjectRoleId(), item.getProjectId()); + if (permissionsList != null && permissionsList.size() > 0) { + ProjectRolePermission lastRule = permissionsList.get(permissionsList.size() - 1); + item.setSortOrder(lastRule.getSortOrder() + 1); + } + return super.persist(item); + } +} \ No newline at end of file diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java index c9aeba1c5998..7b55c0a81a02 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java @@ -17,13 +17,14 @@ package org.apache.cloudstack.acl.dao; -import com.cloud.utils.db.GenericDao; +import java.util.List; + +import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.RolePermission.Permission; import org.apache.cloudstack.acl.RolePermissionVO; -import java.util.List; +import com.cloud.utils.db.GenericDao; public interface RolePermissionsDao extends GenericDao { /** diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java index 68b6abf21953..d092bf3a3d6a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java @@ -17,6 +17,19 @@ package org.apache.cloudstack.acl.dao; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RolePermission; +import org.apache.cloudstack.acl.RolePermissionVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + import com.cloud.utils.db.Attribute; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; @@ -27,18 +40,6 @@ import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.db.UpdateBuilder; import com.cloud.utils.exception.CloudRuntimeException; -import org.apache.cloudstack.acl.Role; -import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.RolePermission.Permission; -import org.apache.cloudstack.acl.RolePermissionVO; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; @Component public class RolePermissionsDaoImpl extends GenericDaoBase implements RolePermissionsDao { diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 7faf85cef4b5..9ab5774b301b 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -41,6 +41,8 @@ + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql new file mode 100644 index 000000000000..c8f167c451da --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql @@ -0,0 +1,20 @@ +-- 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. + +--; +-- Schema upgrade cleanup from 4.14.0.0 to 4.15.0.0 +--; \ No newline at end of file diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql new file mode 100644 index 000000000000..c8570c64e23a --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -0,0 +1,101 @@ +-- 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. + +--; +-- Schema upgrade from 4.14.0.0 to 4.15.0.0 +--; +-- [ADD/DELETE USER TO PROJECT] add role permission for adding user to project + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; + + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteUserFromProject', 'ALLOW', 71) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteUserFromProject', 'ALLOW', 69) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteUserFromProject', 'ALLOW', 55) ON DUPLICATE KEY UPDATE rule=rule; + +-- [PROJECT ROLE] add role permissions for the CRUD API commands for projectRole + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'createProjectRole', 'ALLOW', 51) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteProjectRole', 'ALLOW', 98) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'listProjectRoles', 'ALLOW', 201) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'updateProjectRole', 'ALLOW', 301) ON DUPLICATE KEY UPDATE rule=rule; + + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'createProjectRole', 'ALLOW', 49) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteProjectRole', 'ALLOW', 93) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'listProjectRoles', 'ALLOW', 188) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'updateProjectRole', 'ALLOW', 285) ON DUPLICATE KEY UPDATE rule=rule; + + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'createProjectRole', 'ALLOW', 39) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteProjectRole', 'ALLOW', 78) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'listProjectRoles', 'ALLOW', 161) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'updateProjectRole', 'ALLOW', 246) ON DUPLICATE KEY UPDATE rule=rule; + +-- [PROJECT ROLE PERMISSION] add role permissions for the CRUD API commands for projectRolePermission + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'createProjectRolePermission', 'ALLOW', 52) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteProjectRolePermission', 'ALLOW', 99) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'listProjectRolePermissions', 'ALLOW', 202) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'updateProjectRolePermission', 'ALLOW', 302) ON DUPLICATE KEY UPDATE rule=rule; + + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'createProjectRolePermission', 'ALLOW', 50) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteProjectRolePermission', 'ALLOW', 94) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'listProjectRolePermissions', 'ALLOW', 189) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'updateProjectRolePermission', 'ALLOW', 286) ON DUPLICATE KEY UPDATE rule=rule; + + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'createProjectRolePermission', 'ALLOW', 40) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteProjectRolePermission', 'ALLOW', 79) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'listProjectRolePermissions', 'ALLOW', 162) ON DUPLICATE KEY UPDATE rule=rule; + INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'updateProjectRolePermission', 'ALLOW', 247) ON DUPLICATE KEY UPDATE rule=rule; + +-- Project roles +CREATE TABLE IF NOT EXISTS `cloud`.`project_role` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(255) UNIQUE, + `name` varchar(255) COMMENT 'unique name of the dynamic project role', + `removed` datetime COMMENT 'date removed', + `description` text COMMENT 'description of the project role', + `project_id` bigint(20) unsigned COMMENT 'Id of the project to which the role belongs', + PRIMARY KEY (`id`), + KEY `i_project_role__name` (`name`), + UNIQUE KEY (`name`), + CONSTRAINT `fk_project_role__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Project role permissions table +CREATE TABLE IF NOT EXISTS `cloud`.`project_role_permissions` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(255) UNIQUE, + `project_id` bigint(20) unsigned NOT NULL COMMENT 'id of the role', + `project_role_id` bigint(20) unsigned NOT NULL COMMENT 'id of the role', + `rule` varchar(255) NOT NULL COMMENT 'rule for the role, api name or wildcard', + `permission` varchar(255) NOT NULL COMMENT 'access authority, allow or deny', + `description` text COMMENT 'description of the rule', + `sort_order` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT 'permission sort order', + PRIMARY KEY (`id`), + KEY `fk_project_role_permissions__project_role_id` (`project_role_id`), + KEY `i_project_role_permissions__sort_order` (`sort_order`), + UNIQUE KEY (`project_role_id`, `rule`), + CONSTRAINT `fk_project_role_permissions__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_role_permissions__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Alter project accounts table to include user_id and project_role_id for role based users in projects +ALTER TABLE `cloud`.`project_account` + ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, + ADD CONSTRAINT `fk_project_account__user_id` FOREIGN KEY `fk_project_account__user_id`(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `project_account_id`, + ADD CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), + -- DROP INDEX `account_id`, + ADD UNIQUE (`user_id`, `account_id`, `project_id`); \ No newline at end of file diff --git a/engine/service/pom.xml b/engine/service/pom.xml index cfcf8db37069..28298fc6dd0c 100644 --- a/engine/service/pom.xml +++ b/engine/service/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT cloud-engine-service war diff --git a/engine/storage/cache/pom.xml b/engine/storage/cache/pom.xml index ce903053e6c5..5735387050a4 100644 --- a/engine/storage/cache/pom.xml +++ b/engine/storage/cache/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml index de531971d941..30255aa7844d 100644 --- a/engine/storage/configdrive/pom.xml +++ b/engine/storage/configdrive/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/datamotion/pom.xml b/engine/storage/datamotion/pom.xml index 7e4b862be061..2e2c34b731cc 100644 --- a/engine/storage/datamotion/pom.xml +++ b/engine/storage/datamotion/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/image/pom.xml b/engine/storage/image/pom.xml index 5a7ac3c33be1..db34b6364e17 100644 --- a/engine/storage/image/pom.xml +++ b/engine/storage/image/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/integration-test/pom.xml b/engine/storage/integration-test/pom.xml index e1eea89b3802..0b341ab01855 100644 --- a/engine/storage/integration-test/pom.xml +++ b/engine/storage/integration-test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/pom.xml b/engine/storage/pom.xml index cdbaad974d0c..1862245c393c 100644 --- a/engine/storage/pom.xml +++ b/engine/storage/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/engine/storage/snapshot/pom.xml b/engine/storage/snapshot/pom.xml index f8949d42c9ec..f7aadcc62c95 100644 --- a/engine/storage/snapshot/pom.xml +++ b/engine/storage/snapshot/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/volume/pom.xml b/engine/storage/volume/pom.xml index 54c522d88fe5..377b73034a1d 100644 --- a/engine/storage/volume/pom.xml +++ b/engine/storage/volume/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/framework/agent-lb/pom.xml b/framework/agent-lb/pom.xml index b4c0407c584b..7d002598ae44 100644 --- a/framework/agent-lb/pom.xml +++ b/framework/agent-lb/pom.xml @@ -24,7 +24,7 @@ cloudstack-framework org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/ca/pom.xml b/framework/ca/pom.xml index 08d583fe1aa8..39601e35e2ba 100644 --- a/framework/ca/pom.xml +++ b/framework/ca/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/cluster/pom.xml b/framework/cluster/pom.xml index 9135b02a703f..1dff1c2bba68 100644 --- a/framework/cluster/pom.xml +++ b/framework/cluster/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/config/pom.xml b/framework/config/pom.xml index b1dd87a01f18..d877599caa98 100644 --- a/framework/config/pom.xml +++ b/framework/config/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/db/pom.xml b/framework/db/pom.xml index 321db816305d..463542b63c2b 100644 --- a/framework/db/pom.xml +++ b/framework/db/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/direct-download/pom.xml b/framework/direct-download/pom.xml index 86d51996d059..5d64d7c6834a 100644 --- a/framework/direct-download/pom.xml +++ b/framework/direct-download/pom.xml @@ -24,7 +24,7 @@ cloudstack-framework org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml \ No newline at end of file diff --git a/framework/events/pom.xml b/framework/events/pom.xml index 8a93709b9136..b821439b0aa8 100644 --- a/framework/events/pom.xml +++ b/framework/events/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/ipc/pom.xml b/framework/ipc/pom.xml index 6cc44fea94a3..d0a815bef3b2 100644 --- a/framework/ipc/pom.xml +++ b/framework/ipc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/jobs/pom.xml b/framework/jobs/pom.xml index 5e329daf798e..bd279ee1e456 100644 --- a/framework/jobs/pom.xml +++ b/framework/jobs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/managed-context/pom.xml b/framework/managed-context/pom.xml index 3cadd224380d..5629085e1d51 100644 --- a/framework/managed-context/pom.xml +++ b/framework/managed-context/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/framework/pom.xml b/framework/pom.xml index 920e2ceebeae..8c36a3efc318 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/framework/quota/pom.xml b/framework/quota/pom.xml index bff58b13f1d3..99a15c916c7a 100644 --- a/framework/quota/pom.xml +++ b/framework/quota/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/rest/pom.xml b/framework/rest/pom.xml index 0b3c0964b62c..88828bc59e7d 100644 --- a/framework/rest/pom.xml +++ b/framework/rest/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml cloud-framework-rest diff --git a/framework/security/pom.xml b/framework/security/pom.xml index b2c1b0edc016..9be699e69bd7 100644 --- a/framework/security/pom.xml +++ b/framework/security/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/framework/spring/lifecycle/pom.xml b/framework/spring/lifecycle/pom.xml index 468bfc785615..a69d350dbee8 100644 --- a/framework/spring/lifecycle/pom.xml +++ b/framework/spring/lifecycle/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/framework/spring/module/pom.xml b/framework/spring/module/pom.xml index a20ec26edf7f..81c9a2930e47 100644 --- a/framework/spring/module/pom.xml +++ b/framework/spring/module/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/acl/dynamic-role-based/pom.xml b/plugins/acl/dynamic-role-based/pom.xml index 44209f7db981..7b47538c5aaf 100644 --- a/plugins/acl/dynamic-role-based/pom.xml +++ b/plugins/acl/dynamic-role-based/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java index d8612a692f8c..e8dfb6415135 100644 --- a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java +++ b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java @@ -25,10 +25,11 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.exception.UnavailableCommandException; import org.apache.cloudstack.api.APICommand; +import org.apache.log4j.Logger; import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.UnavailableCommandException; import com.cloud.user.Account; import com.cloud.user.AccountService; import com.cloud.user.User; @@ -44,7 +45,9 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API private RoleService roleService; private List services; - private Map> annotationRoleBasedApisMap = new HashMap<>(); + private Map> annotationRoleBasedApisMap = new HashMap>(); + + private static final Logger logger = Logger.getLogger(DynamicRoleBasedAPIAccessChecker.class.getName()); protected DynamicRoleBasedAPIAccessChecker() { super(); @@ -84,7 +87,7 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie // Check against current list of permissions for (final RolePermission permission : roleService.findAllPermissionsBy(accountRole.getId())) { if (permission.getRule().matches(commandName)) { - if (RolePermission.Permission.ALLOW.equals(permission.getPermission())) { + if (Permission.ALLOW.equals(permission.getPermission())) { return true; } else { denyApiAccess(commandName); diff --git a/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java b/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java index 12ebbe56f5fe..48b401fcd6d6 100644 --- a/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java +++ b/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java @@ -16,13 +16,9 @@ // under the License. package org.apache.cloudstack.acl; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.user.Account; -import com.cloud.user.AccountService; -import com.cloud.user.AccountVO; -import com.cloud.user.User; -import com.cloud.user.UserVO; -import junit.framework.TestCase; +import java.lang.reflect.Field; +import java.util.Collections; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,8 +26,14 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import java.lang.reflect.Field; -import java.util.Collections; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; + +import junit.framework.TestCase; @RunWith(MockitoJUnitRunner.class) public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase { @@ -117,7 +119,7 @@ public void testInvalidRolePermissionsCheckAccess() { @Test public void testValidAllowRolePermissionApiCheckAccess() { final String allowedApiName = "someAllowedApi"; - final RolePermission permission = new RolePermissionVO(1L, allowedApiName, RolePermission.Permission.ALLOW, null); + final RolePermission permission = new RolePermissionVO(1L, allowedApiName, Permission.ALLOW, null); Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission)); assertTrue(apiAccessChecker.checkAccess(getTestUser(), allowedApiName)); } @@ -125,7 +127,7 @@ public void testValidAllowRolePermissionApiCheckAccess() { @Test public void testValidAllowRolePermissionWildcardCheckAccess() { final String allowedApiName = "someAllowedApi"; - final RolePermission permission = new RolePermissionVO(1L, "some*", RolePermission.Permission.ALLOW, null); + final RolePermission permission = new RolePermissionVO(1L, "some*", Permission.ALLOW, null); Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission)); assertTrue(apiAccessChecker.checkAccess(getTestUser(), allowedApiName)); } @@ -133,7 +135,7 @@ public void testValidAllowRolePermissionWildcardCheckAccess() { @Test public void testValidDenyRolePermissionApiCheckAccess() { final String denyApiName = "someDeniedApi"; - final RolePermission permission = new RolePermissionVO(1L, denyApiName, RolePermission.Permission.DENY, null); + final RolePermission permission = new RolePermissionVO(1L, denyApiName, Permission.DENY, null); Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission)); try { apiAccessChecker.checkAccess(getTestUser(), denyApiName); @@ -145,7 +147,7 @@ public void testValidDenyRolePermissionApiCheckAccess() { @Test public void testValidDenyRolePermissionWildcardCheckAccess() { final String denyApiName = "someDenyApi"; - final RolePermission permission = new RolePermissionVO(1L, "*Deny*", RolePermission.Permission.DENY, null); + final RolePermission permission = new RolePermissionVO(1L, "*Deny*", Permission.DENY, null); Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission)); try { apiAccessChecker.checkAccess(getTestUser(), denyApiName); diff --git a/plugins/acl/project-role-based/pom.xml b/plugins/acl/project-role-based/pom.xml new file mode 100644 index 000000000000..b8f373d2c6ad --- /dev/null +++ b/plugins/acl/project-role-based/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + cloud-plugin-acl-project-role-based + Apache CloudStack Plugin - ACL Project Role Based + + org.apache.cloudstack + cloudstack-plugins + 4.15.0.0-SNAPSHOT + ../../pom.xml + + diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java new file mode 100644 index 000000000000..13f5452ed4d9 --- /dev/null +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -0,0 +1,139 @@ +// 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.acl; + +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.UnavailableCommandException; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.User; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.component.PluggableService; + +public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements APIAclChecker { + + @Inject + ProjectAccountDao projectAccountDao; + @Inject + ProjectRoleService projectRoleService; + @Inject + RoleService roleService; + @Inject + AccountService accountService; + + private List services; + private static final Logger LOGGER = Logger.getLogger(ProjectRoleBasedApiAccessChecker.class.getName()); + protected ProjectRoleBasedApiAccessChecker() { + super(); + } + + private void denyApiAccess(final String commandName) throws PermissionDeniedException { + throw new PermissionDeniedException("The API " + commandName + " is blacklisted for the user's/account's project role."); + } + + + public boolean isDisabled() { + return !roleService.isEnabled(); + } + + @Override + public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException { + if (isDisabled()) { + return true; + } + + Account userAccount = accountService.getAccount(user.getAccountId()); + Project project = CallContext.current().getProject(); + if (project == null) { + return true; + } + + ProjectAccount projectUser = projectAccountDao.findByProjectIdUserId(project.getId(), userAccount.getAccountId(), user.getId()); + if (projectUser != null) { + if (projectUser.getAccountRole() == ProjectAccount.Role.Admin) { + return true; + } else { + return isPermitted(project, projectUser, apiCommandName); + } + } + + ProjectAccount projectAccount = projectAccountDao.findByProjectIdAccountId(project.getId(), userAccount.getAccountId()); + if (projectAccount != null) { + if (projectAccount.getAccountRole() == ProjectAccount.Role.Admin) { + return true; + } else { + return isPermitted(project, projectAccount, apiCommandName); + } + } + // Default deny all + throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account."); + } + + private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { + ProjectRole projectRole = null; + if(projectUser.getProjectRoleId() != null) { + projectRole = projectRoleService.findProjectRole(projectUser.getProjectRoleId(), project.getId()); + } + + if (projectRole == null) { + return true; + } + + for (ProjectRolePermission permission : projectRoleService.findAllProjectRolePermissions(project.getId(), projectRole.getId())) { + if (permission.getRule().matches(apiCommandName)) { + if (Permission.ALLOW.equals(permission.getPermission())) { + return true; + } else { + denyApiAccess(apiCommandName); + } + } + } + throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account."); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + return true; + } + + @Override + public boolean start() { + return super.start(); + } + + public List getServices() { + return services; + } + + @Inject + public void setServices(List services) { + this.services = services; + } +} diff --git a/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties new file mode 100644 index 000000000000..76064d4ccf60 --- /dev/null +++ b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties @@ -0,0 +1,18 @@ +# 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. +name=acl-project-role-based +parent=api \ No newline at end of file diff --git a/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/spring-acl-project-role-based-context.xml b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/spring-acl-project-role-based-context.xml new file mode 100644 index 000000000000..8aa185db1836 --- /dev/null +++ b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/spring-acl-project-role-based-context.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/plugins/acl/static-role-based/pom.xml b/plugins/acl/static-role-based/pom.xml index a5745bbb7e59..5a7db6ba3630 100644 --- a/plugins/acl/static-role-based/pom.xml +++ b/plugins/acl/static-role-based/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/explicit-dedication/pom.xml b/plugins/affinity-group-processors/explicit-dedication/pom.xml index 2308a2cdc2ef..fb37bbcd4f55 100644 --- a/plugins/affinity-group-processors/explicit-dedication/pom.xml +++ b/plugins/affinity-group-processors/explicit-dedication/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/host-affinity/pom.xml b/plugins/affinity-group-processors/host-affinity/pom.xml index 6f1b271ee287..04406c228c11 100644 --- a/plugins/affinity-group-processors/host-affinity/pom.xml +++ b/plugins/affinity-group-processors/host-affinity/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/host-anti-affinity/pom.xml b/plugins/affinity-group-processors/host-anti-affinity/pom.xml index 4d82d60e1d38..45d98c8a1be4 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/pom.xml +++ b/plugins/affinity-group-processors/host-anti-affinity/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/alert-handlers/snmp-alerts/pom.xml b/plugins/alert-handlers/snmp-alerts/pom.xml index 2955c8076637..fef30f60ae83 100644 --- a/plugins/alert-handlers/snmp-alerts/pom.xml +++ b/plugins/alert-handlers/snmp-alerts/pom.xml @@ -24,7 +24,7 @@ cloudstack-plugins org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/alert-handlers/syslog-alerts/pom.xml b/plugins/alert-handlers/syslog-alerts/pom.xml index a86b2ce79570..c011305ac239 100644 --- a/plugins/alert-handlers/syslog-alerts/pom.xml +++ b/plugins/alert-handlers/syslog-alerts/pom.xml @@ -24,7 +24,7 @@ cloudstack-plugins org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/discovery/pom.xml b/plugins/api/discovery/pom.xml index f7be6dd5f7ad..32e50f92bf9d 100644 --- a/plugins/api/discovery/pom.xml +++ b/plugins/api/discovery/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java index 62164db88ca4..c16d1b233244 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java @@ -16,13 +16,16 @@ // under the License. package org.apache.cloudstack.discovery; -import com.cloud.serializer.Param; -import com.cloud.user.User; -import com.cloud.utils.ReflectUtil; -import com.cloud.utils.StringUtils; -import com.cloud.utils.component.ComponentLifecycleBase; -import com.cloud.utils.component.PluggableService; -import com.google.gson.annotations.SerializedName; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -39,14 +42,13 @@ import org.reflections.ReflectionUtils; import org.springframework.stereotype.Component; -import javax.inject.Inject; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import com.cloud.serializer.Param; +import com.cloud.user.User; +import com.cloud.utils.ReflectUtil; +import com.cloud.utils.StringUtils; +import com.cloud.utils.component.ComponentLifecycleBase; +import com.cloud.utils.component.PluggableService; +import com.google.gson.annotations.SerializedName; @Component public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements ApiDiscoveryService { diff --git a/plugins/api/rate-limit/pom.xml b/plugins/api/rate-limit/pom.xml index 9734f398d88a..176036b9520a 100644 --- a/plugins/api/rate-limit/pom.xml +++ b/plugins/api/rate-limit/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/solidfire-intg-test/pom.xml b/plugins/api/solidfire-intg-test/pom.xml index f4332a081ec9..220a66c27cdd 100644 --- a/plugins/api/solidfire-intg-test/pom.xml +++ b/plugins/api/solidfire-intg-test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/vmware-sioc/pom.xml b/plugins/api/vmware-sioc/pom.xml index e6d77c89e65c..e0d3a6d3683b 100644 --- a/plugins/api/vmware-sioc/pom.xml +++ b/plugins/api/vmware-sioc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/dummy/pom.xml b/plugins/backup/dummy/pom.xml index 5d6d7bb5be71..6301e1f5ef62 100644 --- a/plugins/backup/dummy/pom.xml +++ b/plugins/backup/dummy/pom.xml @@ -23,7 +23,7 @@ cloudstack-plugins org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml index e82bf1b921f4..0fb0cb60772a 100644 --- a/plugins/backup/veeam/pom.xml +++ b/plugins/backup/veeam/pom.xml @@ -23,7 +23,7 @@ cloudstack-plugins org.apache.cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/ca/root-ca/pom.xml b/plugins/ca/root-ca/pom.xml index 59c5686a0e6a..400c12e9be59 100644 --- a/plugins/ca/root-ca/pom.xml +++ b/plugins/ca/root-ca/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/database/mysql-ha/pom.xml b/plugins/database/mysql-ha/pom.xml index f089b480a89f..2029253cc878 100644 --- a/plugins/database/mysql-ha/pom.xml +++ b/plugins/database/mysql-ha/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/database/quota/pom.xml b/plugins/database/quota/pom.xml index f470cdc9a203..eebc7a6ce0a5 100644 --- a/plugins/database/quota/pom.xml +++ b/plugins/database/quota/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/dedicated-resources/pom.xml b/plugins/dedicated-resources/pom.xml index a7cd33904795..31a35f43643c 100644 --- a/plugins/dedicated-resources/pom.xml +++ b/plugins/dedicated-resources/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/plugins/deployment-planners/implicit-dedication/pom.xml b/plugins/deployment-planners/implicit-dedication/pom.xml index c83e461e4151..d0091c5de05c 100644 --- a/plugins/deployment-planners/implicit-dedication/pom.xml +++ b/plugins/deployment-planners/implicit-dedication/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/deployment-planners/user-concentrated-pod/pom.xml b/plugins/deployment-planners/user-concentrated-pod/pom.xml index 8c894769ddcc..b15ded0221d4 100644 --- a/plugins/deployment-planners/user-concentrated-pod/pom.xml +++ b/plugins/deployment-planners/user-concentrated-pod/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/deployment-planners/user-dispersing/pom.xml b/plugins/deployment-planners/user-dispersing/pom.xml index f7b5a9a71085..fdcaa4a04c16 100644 --- a/plugins/deployment-planners/user-dispersing/pom.xml +++ b/plugins/deployment-planners/user-dispersing/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/inmemory/pom.xml b/plugins/event-bus/inmemory/pom.xml index e0564cbc0dc8..3ebceccf6ccf 100644 --- a/plugins/event-bus/inmemory/pom.xml +++ b/plugins/event-bus/inmemory/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/kafka/pom.xml b/plugins/event-bus/kafka/pom.xml index 67a5ab9fbefc..9df216a17fd0 100644 --- a/plugins/event-bus/kafka/pom.xml +++ b/plugins/event-bus/kafka/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/rabbitmq/pom.xml b/plugins/event-bus/rabbitmq/pom.xml index 38f522fba663..853c52eec4cb 100644 --- a/plugins/event-bus/rabbitmq/pom.xml +++ b/plugins/event-bus/rabbitmq/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/ha-planners/skip-heurestics/pom.xml b/plugins/ha-planners/skip-heurestics/pom.xml index 75a02278c5c4..645eb4ebe6ac 100644 --- a/plugins/ha-planners/skip-heurestics/pom.xml +++ b/plugins/ha-planners/skip-heurestics/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/host-allocators/random/pom.xml b/plugins/host-allocators/random/pom.xml index 6babad9995e3..778121140c6a 100644 --- a/plugins/host-allocators/random/pom.xml +++ b/plugins/host-allocators/random/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/baremetal/pom.xml b/plugins/hypervisors/baremetal/pom.xml index c7537a909717..aecb9cca3f4f 100755 --- a/plugins/hypervisors/baremetal/pom.xml +++ b/plugins/hypervisors/baremetal/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-baremetal @@ -32,20 +32,20 @@ commons-lang commons-lang - - javax.xml.bind - jaxb-api - ${cs.jaxb.version} - - - com.sun.xml.bind - jaxb-core - ${cs.jaxb.version} - - - com.sun.xml.bind - jaxb-impl - ${cs.jaxb.version} - + + javax.xml.bind + jaxb-api + ${cs.jaxb.version} + + + com.sun.xml.bind + jaxb-core + ${cs.jaxb.version} + + + com.sun.xml.bind + jaxb-impl + ${cs.jaxb.version} + diff --git a/plugins/hypervisors/hyperv/pom.xml b/plugins/hypervisors/hyperv/pom.xml index 07d77aa38377..ccd4ae5f4f59 100644 --- a/plugins/hypervisors/hyperv/pom.xml +++ b/plugins/hypervisors/hyperv/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index 9d0c786cbb4e..7f73d8626cc0 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/ovm/pom.xml b/plugins/hypervisors/ovm/pom.xml index 7b6cff5d979b..6058949db8e1 100644 --- a/plugins/hypervisors/ovm/pom.xml +++ b/plugins/hypervisors/ovm/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/ovm3/pom.xml b/plugins/hypervisors/ovm3/pom.xml index df513e76fa19..011dc1a75232 100644 --- a/plugins/hypervisors/ovm3/pom.xml +++ b/plugins/hypervisors/ovm3/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/simulator/pom.xml b/plugins/hypervisors/simulator/pom.xml index d9aead58c2d8..20208bf98b28 100644 --- a/plugins/hypervisors/simulator/pom.xml +++ b/plugins/hypervisors/simulator/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-simulator diff --git a/plugins/hypervisors/ucs/pom.xml b/plugins/hypervisors/ucs/pom.xml index d515b673d42c..0629acefeb7c 100644 --- a/plugins/hypervisors/ucs/pom.xml +++ b/plugins/hypervisors/ucs/pom.xml @@ -23,7 +23,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-ucs diff --git a/plugins/hypervisors/vmware/pom.xml b/plugins/hypervisors/vmware/pom.xml index 9e52fd167937..f5489488f355 100644 --- a/plugins/hypervisors/vmware/pom.xml +++ b/plugins/hypervisors/vmware/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/xenserver/pom.xml b/plugins/hypervisors/xenserver/pom.xml index 14e150fc0a56..ea5ed14263dd 100644 --- a/plugins/hypervisors/xenserver/pom.xml +++ b/plugins/hypervisors/xenserver/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/cloudian/pom.xml b/plugins/integrations/cloudian/pom.xml index 27861d8810ac..dc949e84cc19 100644 --- a/plugins/integrations/cloudian/pom.xml +++ b/plugins/integrations/cloudian/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/kubernetes-service/pom.xml b/plugins/integrations/kubernetes-service/pom.xml index 9fb2a4391a95..3bdaec9718cd 100644 --- a/plugins/integrations/kubernetes-service/pom.xml +++ b/plugins/integrations/kubernetes-service/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/prometheus/pom.xml b/plugins/integrations/prometheus/pom.xml index dfe87e05477d..d6684ca8baa5 100644 --- a/plugins/integrations/prometheus/pom.xml +++ b/plugins/integrations/prometheus/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/metrics/pom.xml b/plugins/metrics/pom.xml index c0ce79eb90fd..3772ff917faf 100644 --- a/plugins/metrics/pom.xml +++ b/plugins/metrics/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/plugins/network-elements/bigswitch/pom.xml b/plugins/network-elements/bigswitch/pom.xml index c3f626942c5a..3f7018085b9a 100644 --- a/plugins/network-elements/bigswitch/pom.xml +++ b/plugins/network-elements/bigswitch/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/brocade-vcs/pom.xml b/plugins/network-elements/brocade-vcs/pom.xml index 0d3805f0b7b9..279a0d7d1425 100644 --- a/plugins/network-elements/brocade-vcs/pom.xml +++ b/plugins/network-elements/brocade-vcs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/cisco-vnmc/pom.xml b/plugins/network-elements/cisco-vnmc/pom.xml index a3e48d63f087..165e2d50c643 100644 --- a/plugins/network-elements/cisco-vnmc/pom.xml +++ b/plugins/network-elements/cisco-vnmc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/dns-notifier/pom.xml b/plugins/network-elements/dns-notifier/pom.xml index 56bc7de45e12..3aaa35fb80a6 100644 --- a/plugins/network-elements/dns-notifier/pom.xml +++ b/plugins/network-elements/dns-notifier/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml cloud-plugin-example-dns-notifier diff --git a/plugins/network-elements/elastic-loadbalancer/pom.xml b/plugins/network-elements/elastic-loadbalancer/pom.xml index 4c597644b152..e8dafb89fef4 100644 --- a/plugins/network-elements/elastic-loadbalancer/pom.xml +++ b/plugins/network-elements/elastic-loadbalancer/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/f5/pom.xml b/plugins/network-elements/f5/pom.xml index ff5cfbfe7698..de21ab4db1d4 100644 --- a/plugins/network-elements/f5/pom.xml +++ b/plugins/network-elements/f5/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/globodns/pom.xml b/plugins/network-elements/globodns/pom.xml index f709417e250a..69866cee1420 100644 --- a/plugins/network-elements/globodns/pom.xml +++ b/plugins/network-elements/globodns/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/internal-loadbalancer/pom.xml b/plugins/network-elements/internal-loadbalancer/pom.xml index 7015a62f76fa..0e23eeccd737 100644 --- a/plugins/network-elements/internal-loadbalancer/pom.xml +++ b/plugins/network-elements/internal-loadbalancer/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/juniper-contrail/pom.xml b/plugins/network-elements/juniper-contrail/pom.xml index 7f1dd58fc772..4c916ebd7261 100644 --- a/plugins/network-elements/juniper-contrail/pom.xml +++ b/plugins/network-elements/juniper-contrail/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/juniper-srx/pom.xml b/plugins/network-elements/juniper-srx/pom.xml index 45111bdf1ca6..b2791223e828 100644 --- a/plugins/network-elements/juniper-srx/pom.xml +++ b/plugins/network-elements/juniper-srx/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/netscaler/pom.xml b/plugins/network-elements/netscaler/pom.xml index 0348ab1613d0..22cd0b17521f 100644 --- a/plugins/network-elements/netscaler/pom.xml +++ b/plugins/network-elements/netscaler/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/nicira-nvp/pom.xml b/plugins/network-elements/nicira-nvp/pom.xml index 05af715ea7c5..1965c902b452 100644 --- a/plugins/network-elements/nicira-nvp/pom.xml +++ b/plugins/network-elements/nicira-nvp/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/opendaylight/pom.xml b/plugins/network-elements/opendaylight/pom.xml index 7cec20bc4075..eff859ca145b 100644 --- a/plugins/network-elements/opendaylight/pom.xml +++ b/plugins/network-elements/opendaylight/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/ovs/pom.xml b/plugins/network-elements/ovs/pom.xml index e097666b6ad8..ec417e0b8ea9 100644 --- a/plugins/network-elements/ovs/pom.xml +++ b/plugins/network-elements/ovs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/palo-alto/pom.xml b/plugins/network-elements/palo-alto/pom.xml index 851ee54f5a3c..dfe83e9ff867 100644 --- a/plugins/network-elements/palo-alto/pom.xml +++ b/plugins/network-elements/palo-alto/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/stratosphere-ssp/pom.xml b/plugins/network-elements/stratosphere-ssp/pom.xml index 45083e7202b5..05dbf6db89a9 100644 --- a/plugins/network-elements/stratosphere-ssp/pom.xml +++ b/plugins/network-elements/stratosphere-ssp/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/vxlan/pom.xml b/plugins/network-elements/vxlan/pom.xml index 0193286852c9..6a8f07b292e5 100644 --- a/plugins/network-elements/vxlan/pom.xml +++ b/plugins/network-elements/vxlan/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml index fe2dc27f705d..556f15b22f79 100644 --- a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml +++ b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml index 1d15052db5e3..cfa69a9e59de 100644 --- a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml +++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/pom.xml b/plugins/pom.xml index 524e4a13ecef..a6ef8f4156f0 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT @@ -46,6 +46,7 @@ acl/dynamic-role-based acl/static-role-based + acl/project-role-based affinity-group-processors/explicit-dedication affinity-group-processors/host-affinity diff --git a/plugins/storage-allocators/random/pom.xml b/plugins/storage-allocators/random/pom.xml index 5943e1674961..0f1d816c3fd7 100644 --- a/plugins/storage-allocators/random/pom.xml +++ b/plugins/storage-allocators/random/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/storage/image/default/pom.xml b/plugins/storage/image/default/pom.xml index 26fe92954ea0..ddac09b73735 100644 --- a/plugins/storage/image/default/pom.xml +++ b/plugins/storage/image/default/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/s3/pom.xml b/plugins/storage/image/s3/pom.xml index fffa124a9e79..90a9de8f531b 100644 --- a/plugins/storage/image/s3/pom.xml +++ b/plugins/storage/image/s3/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/sample/pom.xml b/plugins/storage/image/sample/pom.xml index 3a9487dd305f..cc3e2de78724 100644 --- a/plugins/storage/image/sample/pom.xml +++ b/plugins/storage/image/sample/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/swift/pom.xml b/plugins/storage/image/swift/pom.xml index ec633a6c4f51..d8e2d0e6e0ae 100644 --- a/plugins/storage/image/swift/pom.xml +++ b/plugins/storage/image/swift/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/cloudbyte/pom.xml b/plugins/storage/volume/cloudbyte/pom.xml index 0c32e0e08fdb..1d2b156cb312 100644 --- a/plugins/storage/volume/cloudbyte/pom.xml +++ b/plugins/storage/volume/cloudbyte/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/datera/pom.xml b/plugins/storage/volume/datera/pom.xml index ad80fd630ecc..1df71900f3c5 100644 --- a/plugins/storage/volume/datera/pom.xml +++ b/plugins/storage/volume/datera/pom.xml @@ -16,7 +16,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/default/pom.xml b/plugins/storage/volume/default/pom.xml index 8cf466d11141..51450e0f8a84 100644 --- a/plugins/storage/volume/default/pom.xml +++ b/plugins/storage/volume/default/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/nexenta/pom.xml b/plugins/storage/volume/nexenta/pom.xml index 9d42a56602fa..cb090c3b88fe 100644 --- a/plugins/storage/volume/nexenta/pom.xml +++ b/plugins/storage/volume/nexenta/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/sample/pom.xml b/plugins/storage/volume/sample/pom.xml index 078514b8cb60..e41825ff9e5b 100644 --- a/plugins/storage/volume/sample/pom.xml +++ b/plugins/storage/volume/sample/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/solidfire/pom.xml b/plugins/storage/volume/solidfire/pom.xml index 8de120186fe2..65d27625ad06 100644 --- a/plugins/storage/volume/solidfire/pom.xml +++ b/plugins/storage/volume/solidfire/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index c1db2e30aafc..04e5eb7d9f53 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/md5/pom.xml b/plugins/user-authenticators/md5/pom.xml index 3d5402c46594..ef7d20f7f352 100644 --- a/plugins/user-authenticators/md5/pom.xml +++ b/plugins/user-authenticators/md5/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/pbkdf2/pom.xml b/plugins/user-authenticators/pbkdf2/pom.xml index ce16bae80af1..6a39a8034367 100644 --- a/plugins/user-authenticators/pbkdf2/pom.xml +++ b/plugins/user-authenticators/pbkdf2/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/plain-text/pom.xml b/plugins/user-authenticators/plain-text/pom.xml index 81f93cba3984..90d894c0c9b0 100644 --- a/plugins/user-authenticators/plain-text/pom.xml +++ b/plugins/user-authenticators/plain-text/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml index b168af2b1e16..2ceae43276b4 100644 --- a/plugins/user-authenticators/saml2/pom.xml +++ b/plugins/user-authenticators/saml2/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/sha256salted/pom.xml b/plugins/user-authenticators/sha256salted/pom.xml index be116d0f93ec..73aeb9a14003 100644 --- a/plugins/user-authenticators/sha256salted/pom.xml +++ b/plugins/user-authenticators/sha256salted/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 65928f1aa929..b910ed7758d6 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT pom Apache CloudStack Apache CloudStack is an IaaS ("Infrastructure as a Service") cloud orchestration platform. diff --git a/quickcloud/pom.xml b/quickcloud/pom.xml index 8430d9b41b0d..24910237fd0d 100644 --- a/quickcloud/pom.xml +++ b/quickcloud/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/server/pom.xml b/server/pom.xml index deadd28a1dc3..7cd0dd1460d3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 5184d660e9b8..22d57c7f0fb6 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -23,7 +23,9 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.dc.DataCenter; @@ -40,6 +42,9 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.ProjectAccountVO; import com.cloud.projects.ProjectManager; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; @@ -64,7 +69,7 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { @Inject ProjectManager _projectMgr; @Inject - ProjectAccountDao _projecAccountDao; + ProjectAccountDao _projectAccountDao; @Inject NetworkModel _networkMgr; @Inject @@ -80,6 +85,7 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { @Inject VpcOfferingDetailsDao vpcOfferingDetailsDao; + public static final Logger s_logger = Logger.getLogger(DomainChecker.class.getName()); protected DomainChecker() { super(); } @@ -107,6 +113,7 @@ public boolean checkAccess(User user, Domain domain) throws PermissionDeniedExce if (user.getRemoved() != null) { throw new PermissionDeniedException(user + " is no longer active."); } + Account account = _accountDao.findById(user.getAccountId()); return checkAccess(account, domain); } @@ -154,7 +161,6 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a } else { if (_accountService.isNormalUser(caller.getId())) { Account account = _accountDao.findById(entity.getAccountId()); - if (account != null && account.getType() == Account.ACCOUNT_TYPE_PROJECT) { //only project owner can delete/modify the project if (accessType != null && accessType == AccessType.ModifyProject) { @@ -171,7 +177,6 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a } } } - return true; } @@ -414,6 +419,35 @@ else if (_accountService.isDomainAdmin(account.getId())) { return false; } + private void checkProjectAccess(Account caller) { + User user = CallContext.current().getCallingUser(); + Project project = CallContext.current().getProject(); + if (project == null) { + return; + } + ProjectAccountVO projectAccountVO = _projectAccountDao.findByProjectIdUserId(project.getId(), caller.getAccountId(), user.getId()); + if (projectAccountVO != null) { + if (isProjectAdmin(projectAccountVO)) { + return; + } + throw new PermissionDeniedException(String.format("User %s of account %s doesn't have permission in project %s", user.getId(), caller.getId(), project.getId())); + } + projectAccountVO = _projectAccountDao.findByProjectIdAccountId(project.getId(), caller.getAccountId()); + if (projectAccountVO != null) { + if (isProjectAdmin(projectAccountVO)) { + return; + } + throw new PermissionDeniedException(String.format("Account %s doesn't have permission in project %s", caller.getId(), project.getId())); + } + } + + private boolean isProjectAdmin(ProjectAccountVO projectAccount) { + if (projectAccount.getAccountRole() != ProjectAccount.Role.Admin) { + throw new PermissionDeniedException("Current user is not permitted to perform the operation in the project"); + } + return true; + } + @Override public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) throws PermissionDeniedException { diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 8d476ba78dd7..e6a1be6ff49a 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -1833,6 +1833,7 @@ public static ProjectResponse newProjectResponse(EnumSet details, } public static List newProjectView(Project proj) { + return s_projectJoinDao.newProjectView(proj); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 9bec40894c93..95eace6c2420 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -31,7 +31,6 @@ import javax.inject.Inject; -import com.cloud.resource.RollingMaintenanceManager; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -45,9 +44,6 @@ import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; -import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse; -import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse; -import org.apache.cloudstack.api.response.RollingMaintenanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse; @@ -68,7 +64,6 @@ import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.ExtractResponse; @@ -116,6 +111,10 @@ import org.apache.cloudstack.api.response.ResourceCountResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse; +import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse; +import org.apache.cloudstack.api.response.RollingMaintenanceResponse; +import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.SSHKeyPairResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; @@ -296,6 +295,7 @@ import com.cloud.projects.ProjectAccount; import com.cloud.projects.ProjectInvitation; import com.cloud.region.ha.GlobalLoadBalancerRule; +import com.cloud.resource.RollingMaintenanceManager; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.service.ServiceOfferingVO; diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index 7d096b869a8f..0879ace99865 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -16,49 +16,45 @@ // under the License. package com.cloud.api; -import com.cloud.api.dispatch.DispatchChainFactory; -import com.cloud.api.dispatch.DispatchTask; -import com.cloud.api.response.ApiResponseSerializer; -import com.cloud.domain.Domain; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; -import com.cloud.event.ActionEventUtils; -import com.cloud.event.EventCategory; -import com.cloud.event.EventTypes; -import com.cloud.exception.AccountLimitException; -import com.cloud.exception.CloudAuthenticationException; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.OriginDeniedException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.RequestLimitException; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.UnavailableCommandException; -import com.cloud.storage.VolumeApiService; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.DomainManager; -import com.cloud.user.User; -import com.cloud.user.UserAccount; -import com.cloud.user.UserVO; -import com.cloud.utils.ConstantTimeComparator; -import com.cloud.utils.DateUtil; -import com.cloud.utils.HttpUtils; -import com.cloud.utils.Pair; -import com.cloud.utils.ReflectUtil; -import com.cloud.utils.StringUtils; -import com.cloud.utils.net.NetUtils; -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.TransactionLegacy; -import com.cloud.utils.db.UUIDManager; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.exception.ExceptionProxyObject; -import com.google.gson.reflect.TypeToken; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.security.SecureRandom; +import java.security.Security; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -141,43 +137,51 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.stereotype.Component; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.lang.reflect.Type; -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.security.SecureRandom; -import java.security.Security; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import com.cloud.api.dispatch.DispatchChainFactory; +import com.cloud.api.dispatch.DispatchTask; +import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventCategory; +import com.cloud.event.EventTypes; +import com.cloud.exception.AccountLimitException; +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OriginDeniedException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.RequestLimitException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.UnavailableCommandException; +import com.cloud.projects.Project; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.storage.VolumeApiService; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.DomainManager; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import com.cloud.user.UserVO; +import com.cloud.utils.ConstantTimeComparator; +import com.cloud.utils.DateUtil; +import com.cloud.utils.HttpUtils; +import com.cloud.utils.Pair; +import com.cloud.utils.ReflectUtil; +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.TransactionLegacy; +import com.cloud.utils.db.UUIDManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.exception.ExceptionProxyObject; +import com.cloud.utils.net.NetUtils; +import com.google.gson.reflect.TypeToken; @Component public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiServerService, Configurable { @@ -209,6 +213,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer private EntityManager entityMgr; @Inject private APIAuthenticationManager authManager; + @Inject + private ProjectDao projectDao; private List pluggableServices; @@ -843,7 +849,15 @@ public boolean verifyRequest(final Map requestParameters, fina // if userId not null, that mean that user is logged in if (userId != null) { final User user = ApiDBUtils.findUserById(userId); - + for (Map.Entry entry: requestParameters.entrySet()) { + if (entry.getKey().equals(ApiConstants.PROJECT_ID)) { + String projectId = String.valueOf(entry.getValue()[0]); + Project project = projectDao.findByUuid(projectId); + if (project != null) { + CallContext.current().setProject(project); + } + } + } return commandAvailable(remoteAddress, commandName, user); } else { // check against every available command to see if the command exists or not diff --git a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java index b1051a72fdd7..05814891d1bf 100644 --- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -269,20 +270,24 @@ public void processParameters(final BaseCmd cmd, final Map params) { doAccessChecks(cmd, entitiesToAccess); } - private void doAccessChecks(BaseCmd cmd, Map entitiesToAccess) { Account caller = CallContext.current().getCallingAccount(); - // due to deleteAccount design flaw CLOUDSTACK-6588, we should still include those removed account as well to clean up leftover resources from that account - Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); + List entityOwners = cmd.getEntityOwnerIds(); + Account[] owners = null; + if (entityOwners != null) { + owners = entityOwners.stream().map(id -> _accountMgr.getAccount(id)).collect(Collectors.toList()).toArray(new Account[0]); + } else { + owners = new Account[]{_accountMgr.getAccount(cmd.getEntityOwnerId())}; + } if (cmd instanceof BaseAsyncCreateCmd) { // check that caller can access the owner account. - _accountMgr.checkAccess(caller, null, false, owner); + _accountMgr.checkAccess(caller, null, false, owners); } if (!entitiesToAccess.isEmpty()) { // check that caller can access the owner account. - _accountMgr.checkAccess(caller, null, false, owner); + _accountMgr.checkAccess(caller, null, false, owners); for (Map.Entryentry : entitiesToAccess.entrySet()) { Object entity = entry.getKey(); if (entity instanceof ControlledEntity) { diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java index ced81a6e06c4..f01c2c42e1b5 100644 --- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java @@ -26,9 +26,6 @@ import java.util.List; import java.util.Map; -import com.cloud.configuration.Resource; -import com.cloud.domain.Domain; -import org.apache.log4j.Logger; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.DomainDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails; @@ -59,6 +56,7 @@ import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.AccountJoinVO; @@ -84,8 +82,10 @@ import com.cloud.api.query.vo.UserAccountJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.api.query.vo.VolumeJoinVO; -import com.cloud.storage.StoragePoolTagVO; +import com.cloud.configuration.Resource; +import com.cloud.domain.Domain; import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StoragePoolTagVO; import com.cloud.storage.VolumeStats; import com.cloud.user.Account; diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index fb3626bfb6a1..0ae8a8c6cc03 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -27,6 +27,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.mail.Authenticator; @@ -38,7 +39,9 @@ import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; +import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.acl.dao.ProjectRoleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; @@ -74,6 +77,7 @@ import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; @@ -122,6 +126,10 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { private ProjectInvitationJoinDao _projectInvitationJoinDao; @Inject protected ResourceTagDao _resourceTagDao; + @Inject + private ProjectRoleDao projectRoleDao; + @Inject + private UserDao userDao; protected boolean _invitationRequired = false; protected long _invitationTimeOut = 86400000; @@ -171,10 +179,24 @@ public boolean stop() { return true; } + private User validateUser(Long userId, Long accountId, Long domainId) { + User user = null; + if (userId != null) { + user = userDao.findById(userId); + if (user == null ) { + throw new InvalidParameterValueException("Invalid user ID provided"); + } + if (user.getAccountId() != accountId || _accountDao.findById(user.getAccountId()).getDomainId() != domainId) { + throw new InvalidParameterValueException("User doesn't belong to the specified account or domain"); + } + } + return user; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", create = true) @DB - public Project createProject(final String name, final String displayText, String accountName, final Long domainId) throws ResourceAllocationException { + public Project createProject(final String name, final String displayText, String accountName, final Long domainId, final Long userId, final Long accountId) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); Account owner = caller; @@ -188,6 +210,10 @@ public Project createProject(final String name, final String displayText, String throw new InvalidParameterValueException("Account name and domain id must be specified together"); } + if (userId != null && (accountId == null && domainId == null)) { + throw new InvalidParameterValueException("Domain ID and account ID must be provided with User ID"); + } + if (accountName != null) { owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); } @@ -197,10 +223,13 @@ public Project createProject(final String name, final String displayText, String throw new InvalidParameterValueException("Project with name " + name + " already exists in domain id=" + owner.getDomainId()); } + User user = validateUser(userId, accountId, domainId); + //do resource limit check _resourceLimitMgr.checkResourceLimit(owner, ResourceType.project); final Account ownerFinal = owner; + User finalUser = user; return Transaction.execute(new TransactionCallback() { @Override public Project doInTransaction(TransactionStatus status) { @@ -214,7 +243,7 @@ public Project doInTransaction(TransactionStatus status) { Project project = _projectDao.persist(new ProjectVO(name, displayText, ownerFinal.getDomainId(), projectAccount.getId())); //assign owner to the project - assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin); + assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin, (finalUser != null ? finalUser.getId() : null), null); if (project != null) { CallContext.current().setEventDetails("Project id=" + project.getId()); @@ -241,6 +270,7 @@ public Project enableProject(long projectId) { throw new InvalidParameterValueException("Unable to find project by id " + projectId); } + CallContext.current().setProject(project); _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); //at this point enabling project doesn't require anything, so just update the state @@ -261,6 +291,7 @@ public boolean deleteProject(long projectId) { throw new InvalidParameterValueException("Unable to find project by id " + projectId); } + CallContext.current().setProject(project); _accountMgr.checkAccess(ctx.getCallingAccount(), AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); @@ -281,7 +312,6 @@ public Boolean doInTransaction(TransactionStatus status) { if (projectOwner != null) { _resourceLimitMgr.decrementResourceCount(projectOwner.getId(), ResourceType.project); } - return updateResult; } }); @@ -364,8 +394,13 @@ public long getInvitationTimeout() { } @Override - public ProjectAccount assignAccountToProject(Project project, long accountId, ProjectAccount.Role accountRole) { - return _projectAccountDao.persist(new ProjectAccountVO(project, accountId, accountRole)); + public ProjectAccount assignAccountToProject(Project project, long accountId, ProjectAccount.Role accountRole, Long userId, Long projectRoleId) { + ProjectAccountVO projectAccountVO = new ProjectAccountVO(project, accountId, accountRole, userId, projectRoleId); + return _projectAccountDao.persist(projectAccountVO); + } + + public ProjectAccount assignUserToProject(Project project, long userId, long accountId, Role userRole, long projectRoleId) { + return assignAccountToProject(project, accountId, userRole, userId, projectRoleId); } @Override @@ -404,6 +439,15 @@ public Account getProjectOwner(long projectId) { return null; } + @Override + public List getProjectOwners(long projectId) { + List projectAccounts = _projectAccountDao.getProjectOwners(projectId); + if (projectAccounts != null || !projectAccounts.isEmpty()) { + return projectAccounts.stream().map(acc -> _accountMgr.getAccount(acc.getAccountId()).getAccountId()).collect(Collectors.toList()); + } + return null; + } + @Override public ProjectVO findByProjectAccountId(long projectAccountId) { return _projectDao.findByProjectAccountId(projectAccountId); @@ -414,6 +458,64 @@ public ProjectVO findByProjectAccountIdIncludingRemoved(long projectAccountId) { return _projectDao.findByProjectAccountIdIncludingRemoved(projectAccountId); } + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_USER_ADD, eventDescription = "adding user to project", async = true) + public boolean addUserToProject(Long projectId, Long userId, Long projectRoleId, Role projectRole) { + Account caller = CallContext.current().getCallingAccount(); + + Project project = getProject(projectId); + if (project == null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); + ex.addProxyObject(String.valueOf(projectId), "projectId"); + throw ex; + } + + if (project.getState() != State.Active) { + InvalidParameterValueException ex = + new InvalidParameterValueException("Can't add user to the specified project id in state=" + project.getState() + " as it isn't currently active"); + ex.addProxyObject(project.getUuid(), "projectId"); + throw ex; + } + + User user = userDao.findById(userId); + if (user == null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Invalid user ID provided"); + ex.addProxyObject(String.valueOf(userId), "userId"); + throw ex; + } + + CallContext.current().setProject(project); + _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); + + Account userAccount = _accountDao.findById(user.getAccountId()); + if (_projectAccountDao.findByProjectIdAccountId(projectId, userAccount.getAccountId()) != null) { + throw new InvalidParameterValueException("User belongs to account " + userAccount.getAccountId() + " which is already part of the project"); + } + + ProjectAccount projectAccountUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); + if (projectAccountUser != null) { + s_logger.info("User with id: " + user.getId() + " is already added to the project with id: " + projectId); + return true; + } + + if (projectRoleId != null && projectRoleId < 1L) { + throw new InvalidParameterValueException("Invalid project role id provided"); + } + + ProjectRole role = null; + if (projectRoleId != null) { + role = projectRoleDao.findById(projectRoleId); + if (role == null || (role != null && role.getProjectId() != projectId)) { + throw new InvalidParameterValueException("Invalid project role ID for the given project"); + } + } + if (assignUserToProject(project, userId, user.getAccountId(), projectRole, role.getId()) != null) { + return true; + } + s_logger.warn("Failed to add user to project with id: "+projectId); + return false; + } + @Override public Project findByNameAndDomainId(String name, long domainId) { return _projectDao.findByNameAndDomain(name, domainId); @@ -443,13 +545,29 @@ public boolean canModifyProjectAccount(Account caller, long accountId) { _accountMgr.checkAccess(caller, _domainDao.findById(owner.getDomainId())); return true; } + + User user = CallContext.current().getCallingUser(); + Project project = CallContext.current().getProject(); + if (project != null) { + ProjectAccountVO projectUser = _projectAccountDao.findByProjectIdUserId(project.getId(), caller.getAccountId(), user.getId()); + if (projectUser != null) { + return _projectAccountDao.canUserModifyProject(project.getId(), caller.getId(), user.getId()); + } + } return _projectAccountDao.canModifyProjectAccount(caller.getId(), accountId); } + private void updateProjectAccount(ProjectAccountVO futureOwner, Role newAccRole, Long accountId) throws ResourceAllocationException { + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(accountId), ResourceType.project); + futureOwner.setAccountRole(newAccRole); + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project); + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true) - public Project updateProject(final long projectId, final String displayText, final String newOwnerName) throws ResourceAllocationException { + public Project updateProject(final long projectId, final String displayText, final String newOwnerName, Long userId, + Long accountId, Long domainId, Role newRole) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); //check that the project exists @@ -459,63 +577,64 @@ public Project updateProject(final long projectId, final String displayText, fin throw new InvalidParameterValueException("Unable to find the project id=" + projectId); } + CallContext.current().setProject(project); //verify permissions _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); + List projectOwners = _projectAccountDao.getProjectOwners(projectId); + Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { - if (displayText != null) { - project.setDisplayText(displayText); - _projectDao.update(projectId, project); - } - - if (newOwnerName != null) { - //check that the new owner exists - Account futureOwnerAccount = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); - if (futureOwnerAccount == null) { - throw new InvalidParameterValueException("Unable to find account name=" + newOwnerName + " in domain id=" + project.getDomainId()); - } - Account currentOwnerAccount = getProjectOwner(projectId); - if (currentOwnerAccount == null) { - s_logger.error("Unable to find the current owner for the project id=" + projectId); - throw new InvalidParameterValueException("Unable to find the current owner for the project id=" + projectId); - } - if (currentOwnerAccount.getId() != futureOwnerAccount.getId()) { - ProjectAccountVO futureOwner = _projectAccountDao.findByProjectIdAccountId(projectId, futureOwnerAccount.getAccountId()); - if (futureOwner == null) { - throw new InvalidParameterValueException("Account " + newOwnerName + - " doesn't belong to the project. Add it to the project first and then change the project's ownership"); + if (displayText != null) { + project.setDisplayText(displayText); + _projectDao.update(projectId, project); } + if (newOwnerName != null) { + //check that the new owner exists + Account updatedAcc = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); + if (updatedAcc == null) { + throw new InvalidParameterValueException("Unable to find account name=" + newOwnerName + " in domain id=" + project.getDomainId()); + } + ProjectAccountVO newProjectAcc = _projectAccountDao.findByProjectIdAccountId(projectId, updatedAcc.getAccountId()); + if (newProjectAcc == null) { + throw new InvalidParameterValueException("Account " + newOwnerName + + " doesn't belong to the project. Add it to the project first and then change the project's ownership"); + } + + if (projectOwners.size() == 1 && projectOwners.get(0).getAccountId() == newProjectAcc.getAccountId() + && newRole != Role.Admin ) { + throw new InvalidParameterValueException("Cannot demote the only admin of the project"); + } - //do resource limit check - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(futureOwnerAccount.getId()), ResourceType.project); + updateProjectAccount(newProjectAcc, newRole, updatedAcc.getId()); + } else if (userId != null) { + User user = validateUser(userId, accountId, domainId); + if (user == null) { + throw new InvalidParameterValueException("Unable to find user= " + user.getUsername() + " in domain id = " + domainId); + } + ProjectAccountVO newProjectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), userId); + if (newProjectUser == null) { + throw new InvalidParameterValueException("User " + userId + + " doesn't belong to the project. Add it to the project first and then change the project's ownership"); + } - //unset the role for the old owner - ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId()); - currentOwner.setAccountRole(Role.Regular); - _projectAccountDao.update(currentOwner.getId(), currentOwner); - _resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project); + if (projectOwners.size() == 1 && newProjectUser.getUserId().equals(projectOwners.get(0).getUserId()) + && newRole != Role.Admin ) { + throw new InvalidParameterValueException("Cannot demote the only admin of the project"); + } - //set new owner - futureOwner.setAccountRole(Role.Admin); - _projectAccountDao.update(futureOwner.getId(), futureOwner); - _resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project); + updateProjectAccount(newProjectUser, newRole, user.getAccountId()); - } else { - s_logger.trace("Future owner " + newOwnerName + "is already the owner of the project id=" + projectId); - } - } + } } }); - return _projectDao.findById(projectId); - } @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_ADD, eventDescription = "adding account to project", async = true) - public boolean addAccountToProject(long projectId, String accountName, String email) { + public boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) { Account caller = CallContext.current().getCallingAccount(); //check that the project exists @@ -550,6 +669,7 @@ public boolean addAccountToProject(long projectId, String accountName, String em throw ex; } + CallContext.current().setProject(project); //verify permissions - only project owner can assign _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); @@ -561,13 +681,25 @@ public boolean addAccountToProject(long projectId, String accountName, String em } } + if (projectRoleId != null && projectRoleId < 1L) { + throw new InvalidParameterValueException("Invalid project role id provided"); + } + + ProjectRole projectRole = null; + if (projectRoleId != null) { + projectRole = projectRoleDao.findById(projectRoleId); + if (projectRole == null || (projectRole != null && projectRole.getProjectId() != projectId)) { + throw new InvalidParameterValueException("Invalid project role ID for the given project"); + } + } + if (_invitationRequired) { return inviteAccountToProject(project, account, email); } else { if (account == null) { throw new InvalidParameterValueException("Account information is required for assigning account to the project"); } - if (assignAccountToProject(project, account.getId(), ProjectAccount.Role.Regular) != null) { + if (assignAccountToProject(project, account.getId(), projectRoleType, null, projectRole != null ? projectRole.getId() : null) != null) { return true; } else { s_logger.warn("Failed to add account " + accountName + " to project id=" + projectId); @@ -628,11 +760,12 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { throw ex; } + CallContext.current().setProject(project); //verify permissions _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); //Check if the account exists in the project - ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); + ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); if (projectAccount == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Account " + accountName + " is not assigned to the project with specified id"); // Use the projectVO object and not the projectAccount object to inject the projectId. @@ -641,6 +774,7 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { } //can't remove the owner of the project + List projectOwners = _projectAccountDao.getProjectOwners(projectId); if (projectAccount.getAccountRole() == Role.Admin) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to delete account " + accountName + @@ -652,6 +786,60 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { return deleteAccountFromProject(projectId, account.getId()); } + @Override + public boolean deleteUserFromProject(long projectId, long userId) { + Account caller = CallContext.current().getCallingAccount(); + //check that the project exists + Project project = getProject(projectId); + + if (project == null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); + ex.addProxyObject(String.valueOf(projectId), "projectId"); + throw ex; + } + + User user = userDao.findById(userId); + if (user == null) { + throw new InvalidParameterValueException("Invalid userId provided"); + } + Account userAcc = _accountDao.findActiveAccountById(user.getAccountId(), project.getDomainId()); + if (userAcc == null) { + InvalidParameterValueException ex = + new InvalidParameterValueException("Unable to find user "+ user.getUsername() + " in domain id=" + project.getDomainId()); + DomainVO domain = ApiDBUtils.findDomainById(project.getDomainId()); + String domainUuid = String.valueOf(project.getDomainId()); + if (domain != null) { + domainUuid = domain.getUuid(); + } + ex.addProxyObject(domainUuid, "domainId"); + throw ex; + } + + CallContext.current().setProject(project); + //verify permissions + _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); + + //Check if the user exists in the project + ProjectAccount projectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); + if (projectUser == null) { + InvalidParameterValueException ex = new InvalidParameterValueException("User " + user.getUsername() + " is not assigned to the project with specified id"); + // Use the projectVO object and not the projectAccount object to inject the projectId. + ex.addProxyObject(project.getUuid(), "projectId"); + throw ex; + } + return deleteUserFromProject(projectId, user); + } + + private boolean deleteUserFromProject(Long projectId, User user) { + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); + return _projectAccountDao.remove(projectAccount.getId()); + } + }); + } + public ProjectInvitation createAccountInvitation(Project project, Long accountId) { if (activeInviteExists(project, accountId, null)) { throw new InvalidParameterValueException("There is already a pending invitation for account id=" + accountId + " to the project id=" + project); @@ -793,7 +981,7 @@ public Boolean doInTransaction(TransactionStatus status) { if (projectAccount != null) { s_logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); } else { - assignAccountToProject(project, accountIdFinal, ProjectAccount.Role.Regular); + assignAccountToProject(project, accountIdFinal, ProjectAccount.Role.Regular, null, null); } } else { s_logger.warn("Failed to update project invitation " + inviteFinal + " with state " + newState); @@ -830,6 +1018,7 @@ public Project activateProject(final long projectId) { throw ex; } + CallContext.current().setProject(project); //verify permissions _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); @@ -871,6 +1060,7 @@ public Project suspendProject(long projectId) throws ConcurrentOperationExceptio throw ex; } + CallContext.current().setProject(project); _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); if (suspendProject(project)) { @@ -1012,6 +1202,7 @@ public boolean deleteProjectInvitation(long id) { //check that the project exists Project project = getProject(invitation.getProjectId()); + CallContext.current().setProject(project); //check permissions - only project owner can remove the invitations _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 1ef96f225bf1..809d9ba3eaff 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -288,7 +288,9 @@ import org.apache.cloudstack.api.command.admin.zone.MarkDefaultZoneForAccountCmd; import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd; import org.apache.cloudstack.api.command.user.account.AddAccountToProjectCmd; +import org.apache.cloudstack.api.command.user.account.AddUserToProjectCmd; import org.apache.cloudstack.api.command.user.account.DeleteAccountFromProjectCmd; +import org.apache.cloudstack.api.command.user.account.DeleteUserFromProjectCmd; import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; @@ -696,6 +698,7 @@ import com.cloud.vm.DiskProfile; import com.cloud.vm.InstanceGroupVO; import com.cloud.vm.SecondaryStorageVmVO; +import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; @@ -708,9 +711,8 @@ import com.cloud.vm.dao.InstanceGroupDao; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.VMInstanceDao; -import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDao; public class ManagementServerImpl extends ManagerBase implements ManagementServer, Configurable { public static final Logger s_logger = Logger.getLogger(ManagementServerImpl.class.getName()); @@ -2802,7 +2804,9 @@ public List> getCommands() { cmdList.add(MarkDefaultZoneForAccountCmd.class); cmdList.add(UpdateZoneCmd.class); cmdList.add(AddAccountToProjectCmd.class); + cmdList.add(AddUserToProjectCmd.class); cmdList.add(DeleteAccountFromProjectCmd.class); + cmdList.add(DeleteUserFromProjectCmd.class); cmdList.add(ListAccountsCmd.class); cmdList.add(ListProjectAccountsCmd.class); cmdList.add(AssociateIPAddrCmd.class); diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 98b4aa8a83e5..e7f13c33969f 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -37,7 +37,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; - import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.QuerySelector; import org.apache.cloudstack.acl.Role; @@ -123,6 +122,8 @@ import com.cloud.offering.ServiceOffering; import com.cloud.projects.Project; import com.cloud.projects.Project.ListProjectResourcesCriteria; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.ProjectAccountVO; import com.cloud.projects.ProjectInvitationVO; import com.cloud.projects.ProjectManager; import com.cloud.projects.ProjectVO; @@ -499,6 +500,35 @@ public void checkAccess(Account caller, AccessType accessType, boolean sameOwner checkAccess(caller, accessType, sameOwner, null, entities); } + private void checkProjectAccess(Account caller) { + User user = CallContext.current().getCallingUser(); + Project project = CallContext.current().getProject(); + if (project == null) { + return; + } + ProjectAccountVO projectAccountVO = _projectAccountDao.findByProjectIdUserId(project.getId(), caller.getAccountId(), user.getId()); + if (projectAccountVO != null) { + if (isProjectAdmin(projectAccountVO)) { + return; + } + throw new PermissionDeniedException(String.format("User %s of account %s doesn't have permission in project %s", user.getId(), caller.getId(), project.getId())); + } + projectAccountVO = _projectAccountDao.findByProjectIdAccountId(project.getId(), caller.getAccountId()); + if (projectAccountVO != null) { + if (isProjectAdmin(projectAccountVO)) { + return; + } + throw new PermissionDeniedException(String.format("Account %s doesn't have permission in project %s", caller.getId(), project.getId())); + } + } + + private boolean isProjectAdmin(ProjectAccountVO projectAccount) { + if (projectAccount.getAccountRole() != ProjectAccount.Role.Admin) { + throw new PermissionDeniedException("Current user is not permitted to perform the operation in the project"); + } + return true; + } + @Override public void checkAccess(Account caller, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) { @@ -507,14 +537,12 @@ public void checkAccess(Account caller, AccessType accessType, boolean sameOwner ControlledEntity prevEntity = null; if (sameOwner) { for (ControlledEntity entity : entities) { - if (sameOwner) { - if (ownerId == null) { - ownerId = entity.getAccountId(); - } else if (ownerId.longValue() != entity.getAccountId()) { - throw new PermissionDeniedException("Entity " + entity + " and entity " + prevEntity + " belong to different accounts"); - } - prevEntity = entity; + if (ownerId == null) { + ownerId = entity.getAccountId(); + } else if (ownerId.longValue() != entity.getAccountId()) { + throw new PermissionDeniedException("Entity " + entity + " and entity " + prevEntity + " belong to different accounts"); } + prevEntity = entity; } } @@ -523,6 +551,8 @@ public void checkAccess(Account caller, AccessType accessType, boolean sameOwner if (s_logger.isTraceEnabled()) { s_logger.trace("No need to make permission check for System/RootAdmin account, returning true"); } + + //checkProjectAccess(caller); return; } diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java new file mode 100644 index 000000000000..42f6867939f1 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -0,0 +1,306 @@ +// 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.acl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.dao.ProjectRoleDao; +import org.apache.cloudstack.acl.dao.ProjectRolePermissionsDao; +import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRoleCmd; +import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRolePermissionCmd; +import org.apache.cloudstack.api.command.admin.acl.project.DeleteProjectRoleCmd; +import org.apache.cloudstack.api.command.admin.acl.project.DeleteProjectRolePermissionCmd; +import org.apache.cloudstack.api.command.admin.acl.project.ListProjectRolePermissionsCmd; +import org.apache.cloudstack.api.command.admin.acl.project.ListProjectRolesCmd; +import org.apache.cloudstack.api.command.admin.acl.project.UpdateProjectRoleCmd; +import org.apache.cloudstack.api.command.admin.acl.project.UpdateProjectRolePermissionCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.ListUtils; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.component.PluggableService; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.base.Strings; + +public class ProjectRoleManagerImpl extends ManagerBase implements ProjectRoleService, PluggableService { + @Inject + ProjectAccountDao projAccDao; + @Inject + ProjectRoleDao projRoleDao; + @Inject + ProjectDao projectDao; + @Inject + AccountDao accountDao; + @Inject + ProjectRolePermissionsDao projRolePermissionsDao; + + private static final Logger LOGGER = Logger.getLogger(ProjectRoleManagerImpl.class); + + private Project validateProjectId(Long projectId) { + Project project = projectDao.findById(projectId); + if (project == null) { + throw new CloudRuntimeException("Invalid project id provided"); + } + return project; + } + + private void checkAccess(Long projectId) { + Project project = validateProjectId(projectId); + CallContext.current().setProject(project); + + if (!isEnabled()) { + throw new PermissionDeniedException("Dynamic api checker is not enabled, aborting role operation"); + } + + User user = getCurrentUser(); + Account callerAcc = accountDao.findById(user.getAccountId()); + + if (callerAcc == null || callerAcc.getRoleId() == null) { + throw new PermissionDeniedException("Restricted API called by an invalid user account"); + } + + ProjectAccount projectAccount = projAccDao.findByProjectIdUserId(projectId, callerAcc.getAccountId(), user.getId()); + if (projectAccount == null) { + projectAccount = projAccDao.findByProjectIdAccountId(projectId, callerAcc.getAccountId()); + if (projectAccount == null) { + throw new PermissionDeniedException("User/Account not part of project"); + } + } + // if (ProjectAccount.Role.Admin != projectAccount.getAccountRole()) { + if (ProjectAccount.Role.Admin != projectAccount.getAccountRole()) { + throw new PermissionDeniedException("User unauthorized to perform operation in the project"); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_CREATE, eventDescription = "creating Project Role") + public ProjectRole createProjectRole(Long projectId, String name, String description) { + checkAccess(projectId); + return Transaction.execute(new TransactionCallback() { + @Override + public ProjectRoleVO doInTransaction(TransactionStatus status) { + return projRoleDao.persist(new ProjectRoleVO(name, description, projectId)); + } + }); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_UPDATE, eventDescription = "updating Project Role") + public ProjectRole updateProjectRole(ProjectRole role, Long projectId, String name, String description) { + checkAccess(projectId); + ProjectRoleVO projectRoleVO = (ProjectRoleVO) role; + if (!Strings.isNullOrEmpty(name)) { + projectRoleVO.setName(name); + } + if (!Strings.isNullOrEmpty(description)) { + projectRoleVO.setDescription(description); + } + projRoleDao.update(role.getId(), projectRoleVO); + return projectRoleVO; + } + + @Override + public boolean isEnabled() { + return RoleService.EnableDynamicApiChecker.value(); + } + + @Override + public ProjectRole findProjectRole(Long roleId, Long projectId) { + if (projectId == null || projectId < 1L || projectDao.findById(projectId) == null) { + LOGGER.warn("Invalid project ID provided"); + return null; + } + + if (roleId != null && roleId < 1L) { + LOGGER.warn(String.format("Project Role ID is invalid [%s]", roleId)); + return null; + } + + ProjectRoleVO role = projRoleDao.findById(roleId); + if (role == null) { + LOGGER.warn(String.format("Project Role not found [id=%s]", roleId)); + return null; + } + Account currentAcc = getCurrentAccount(); + if (!(role.getProjectId().equals(projectId))) { + LOGGER.warn(String.format("Project role : %s doesn't belong to the project" + role.getName())); + return null; + } + + return role; + } + + @Override + public List findProjectRoles(Long projectId) { + if (projectId == null || projectId < 1L || projectDao.findById(projectId) == null) { + LOGGER.warn("Invalid project ID provided"); + return null; + } + return ListUtils.toListOfInterface(projRoleDao.findAllRoles(projectId)); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_CREATE, eventDescription = "Creating Project Role Permission") + public ProjectRolePermission createProjectRolePermission(CreateProjectRolePermissionCmd cmd) { + Long projectId = cmd.getProjectId(); + Long projectRoleId = cmd.getProjectRoleId(); + Rule rule = cmd.getRule(); + Permission permission = cmd.getPermission(); + String description = cmd.getDescription(); + checkAccess(projectId); + return Transaction.execute(new TransactionCallback() { + @Override + public ProjectRolePermissionVO doInTransaction(TransactionStatus status) { + return projRolePermissionsDao.persist(new ProjectRolePermissionVO(projectId, projectRoleId, rule.toString(), permission, description)); + } + }); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_UPDATE_ORDER, eventDescription = "updating Project Role Permission order") + public boolean updateProjectRolePermission(Long projectId, ProjectRole projectRole, List rolePermissionsOrder) { + checkAccess(projectId); + return projectRole != null && rolePermissionsOrder != null && projRolePermissionsDao.update(projectRole, projectId, rolePermissionsOrder); + + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_UPDATE, eventDescription = "updating Project Role Permission") + public boolean updateProjectRolePermission(Long projectId, ProjectRole projectRole, ProjectRolePermission projectRolePermission, Permission newPermission) { + checkAccess(projectId); + return projectRole != null && projRolePermissionsDao.update(projectRole, projectRolePermission, newPermission); + } + + @Override + public ProjectRolePermission findProjectRolePermission(Long projRolePermissionId) { + if (projRolePermissionId == null) { + return null; + } + return projRolePermissionsDao.findById(projRolePermissionId); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_DELETE, eventDescription = "deleting Project Role Permission") + public boolean deleteProjectRolePermission(ProjectRolePermission projectRolePermission) { + checkAccess(projectRolePermission.getProjectId()); + return projRolePermissionsDao.remove(projectRolePermission.getId()); + } + + @Override + public List findAllProjectRolePermissions(Long projectId, Long projectRoleId) { + List permissions = projRolePermissionsDao.findAllByRoleIdSorted(projectRoleId, projectId); + if (permissions != null) { + return new ArrayList<>(permissions); + } + return Collections.emptyList(); + } + + @Override + public List findProjectRolesByName(Long projectId, String roleName) { + List roles = null; + if (StringUtils.isNotBlank(roleName)) { + roles = projRoleDao.findByName(roleName, projectId); + } + return ListUtils.toListOfInterface(roles); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_DELETE, eventDescription = "deleting Project Role") + public boolean deleteProjectRole(ProjectRole role, Long projectId) { + checkAccess(projectId); + if (role == null) { + return false; + } + + Long roleProjectId = role.getProjectId(); + if (role.getProjectId() != null && !roleProjectId.equals(projectId)) { + throw new PermissionDeniedException("Not authorized to delete the given project role"); + } + + List users = projAccDao.listUsersOrAccountsByRole(role.getId()); + if (users != null && users.size() != 0) { + throw new PermissionDeniedException("Found users that have the project role in use, cannot delete the Project Role"); + } + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + List rolePermissions = projRolePermissionsDao.findAllByRoleIdSorted(role.getId(), projectId); + if (rolePermissions != null && !rolePermissions.isEmpty()) { + for (ProjectRolePermission rolePermission : rolePermissions) { + projRolePermissionsDao.remove(rolePermission.getId()); + } + } + if (projRoleDao.remove(role.getId())) { + ProjectRoleVO projRoleVO = projRoleDao.findByIdIncludingRemoved(role.getId()); + projRoleVO.setName(null); + return projRoleDao.update(role.getId(), projRoleVO); + } + return false; + } + }); + } + + protected Account getCurrentAccount() { + return CallContext.current().getCallingAccount(); + } + + private Long getProjectIdOfAccount() { + Project project = projectDao.findByProjectAccountId(getCurrentAccount().getAccountId()); + if (project != null) { + return project.getId(); + } + return null; + } + + protected User getCurrentUser() { + return CallContext.current().getCallingUser(); + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList>(); + cmdList.add(CreateProjectRoleCmd.class); + cmdList.add(ListProjectRolesCmd.class); + cmdList.add(UpdateProjectRoleCmd.class); + cmdList.add(DeleteProjectRoleCmd.class); + cmdList.add(CreateProjectRolePermissionCmd.class); + cmdList.add(ListProjectRolePermissionsCmd.class); + cmdList.add(UpdateProjectRolePermissionCmd.class); + cmdList.add(DeleteProjectRolePermissionCmd.class); + return cmdList; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java index ae471b2486ca..b44c729f4d43 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRolePermissionCmd; +import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRoleCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -69,7 +70,7 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu @Inject private AccountManager accountManager; - private void checkCallerAccess() { + public void checkCallerAccess() { if (!isEnabled()) { throw new PermissionDeniedException("Dynamic api checker is not enabled, aborting role operation"); } @@ -79,7 +80,7 @@ private void checkCallerAccess() { } Role callerRole = findRole(caller.getRoleId()); if (callerRole == null || callerRole.getRoleType() != RoleType.Admin) { - throw new PermissionDeniedException("Restricted API called by an user account of non-Admin role type"); + throw new PermissionDeniedException("Restricted API called by a user account of non-Admin role type"); } } @@ -212,7 +213,7 @@ public Boolean doInTransaction(TransactionStatus status) { @Override @ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_CREATE, eventDescription = "creating Role Permission") - public RolePermission createRolePermission(final Role role, final Rule rule, final RolePermission.Permission permission, final String description) { + public RolePermission createRolePermission(final Role role, final Rule rule, final Permission permission, final String description) { checkCallerAccess(); return Transaction.execute(new TransactionCallback() { @Override @@ -230,7 +231,7 @@ public boolean updateRolePermission(final Role role, final List } @Override - public boolean updateRolePermission(Role role, RolePermission rolePermission, RolePermission.Permission permission) { + public boolean updateRolePermission(Role role, RolePermission rolePermission, Permission permission) { checkCallerAccess(); return role != null && rolePermissionsDao.update(role, rolePermission, permission); } @@ -326,6 +327,7 @@ public List> getCommands() { cmdList.add(ListRolePermissionsCmd.class); cmdList.add(UpdateRolePermissionCmd.class); cmdList.add(DeleteRolePermissionCmd.class); + cmdList.add(CreateProjectRoleCmd.class); return cmdList; } } diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 1c90a97a70f6..91522f3a5009 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -39,6 +39,8 @@ + + diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index 46946aac71b7..3e19e7b4cf5f 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -31,7 +31,7 @@ public class MockProjectManagerImpl extends ManagerBase implements ProjectManager { @Override - public Project createProject(String name, String displayText, String accountName, Long domainId) throws ResourceAllocationException { + public Project createProject(String name, String displayText, String accountName, Long domainId, Long userId, Long accountId) throws ResourceAllocationException { // TODO Auto-generated method stub return null; } @@ -49,7 +49,7 @@ public Project getProject(long id) { } @Override - public ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole) { + public ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole, Long userId, Long projectRoleId) { // TODO Auto-generated method stub return null; } @@ -60,6 +60,12 @@ public Account getProjectOwner(long projectId) { return null; } + @Override + public List getProjectOwners(long projectId) { + // TODO Auto-generated method stub + return null; + } + @Override public boolean unassignAccountFromProject(long projectId, long accountId) { // TODO Auto-generated method stub @@ -79,13 +85,13 @@ public Project findByNameAndDomainId(String name, long domainId) { } @Override - public Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException { + public Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException { // TODO Auto-generated method stub return null; } @Override - public boolean addAccountToProject(long projectId, String accountName, String email) { + public boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType) { // TODO Auto-generated method stub return false; } @@ -96,6 +102,12 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { return false; } + @Override + public boolean deleteUserFromProject(long projectId, long userId) { + // TODO Auto-generated method stub + return false; + } + @Override public boolean updateInvitation(long projectId, String accountName, String token, boolean accept) { // TODO Auto-generated method stub @@ -203,4 +215,10 @@ public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) { return null; } + @Override + public boolean addUserToProject(Long projectId, Long userId, Long projectRoleId, Role projectRole) { + // TODO Auto-generated method stub + return false; + } + } diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index d8415ff59564..8494d18748ca 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -42,6 +42,8 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccountVO; import com.cloud.server.auth.UserAuthenticator; import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; import com.cloud.user.Account.State; @@ -82,6 +84,12 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase { @Mock private Account accountMock; + @Mock + private ProjectAccountVO projectAccountVO; + @Mock + private Project project; + + @Before public void beforeTest() { Mockito.doReturn(accountMockId).when(accountMock).getId(); @@ -360,7 +368,13 @@ public void retrieveAndValidateAccountTestAccountTypeEqualsSystemType() { Mockito.doReturn(Account.ACCOUNT_ID_SYSTEM).when(userVoMock).getAccountId(); Mockito.doReturn(Account.ACCOUNT_ID_SYSTEM).when(accountMock).getId(); Mockito.doReturn(callingAccount).when(_accountDao).findById(Account.ACCOUNT_ID_SYSTEM); - + // TODO - remove: +// CallContext.current().setProject(project); +// Mockito.lenient().doReturn(1L).when(project).getId(); +// Mockito.lenient().doReturn(1L).when(_user).getId(); +// Mockito.lenient().doReturn(1L).when(accountMock).getAccountId(); +// Mockito.lenient().doReturn(projectAccountVO).when(_projectAccountDao).findByProjectIdUserId(anyLong(), anyLong(), anyLong()); +// Mockito.lenient().doReturn(ProjectAccount.Role.Admin).when(projectAccountVO).getProjectRole(); accountManagerImpl.retrieveAndValidateAccount(userVoMock); } diff --git a/services/console-proxy/pom.xml b/services/console-proxy/pom.xml index f230e93fdda7..64ff71c62386 100644 --- a/services/console-proxy/pom.xml +++ b/services/console-proxy/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-services - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/console-proxy/rdpconsole/pom.xml b/services/console-proxy/rdpconsole/pom.xml index f0d116f90199..69b11eda371d 100644 --- a/services/console-proxy/rdpconsole/pom.xml +++ b/services/console-proxy/rdpconsole/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-service-console-proxy - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml index 4bc6593843bd..3fe7262f28cf 100644 --- a/services/console-proxy/server/pom.xml +++ b/services/console-proxy/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-console-proxy - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/pom.xml b/services/pom.xml index 17fa5e6f7e19..cd975b2c2da0 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/controller/pom.xml b/services/secondary-storage/controller/pom.xml index b9fdfa93db88..4da498455cba 100644 --- a/services/secondary-storage/controller/pom.xml +++ b/services/secondary-storage/controller/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-secondary-storage - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/pom.xml b/services/secondary-storage/pom.xml index d78da6cfb0f2..e37e602274bc 100644 --- a/services/secondary-storage/pom.xml +++ b/services/secondary-storage/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-services - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/server/pom.xml b/services/secondary-storage/server/pom.xml index e1d11270cfa5..51d5f5e81455 100644 --- a/services/secondary-storage/server/pom.xml +++ b/services/secondary-storage/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-secondary-storage - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/systemvm/pom.xml b/systemvm/pom.xml index d4a1c6356098..4b71a13b6f3e 100644 --- a/systemvm/pom.xml +++ b/systemvm/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/test/pom.xml b/test/pom.xml index cc9abf21e7c1..d49e3e43bdd8 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/tools/apidoc/pom.xml b/tools/apidoc/pom.xml index f7ab99190f4d..7078c83f7dc0 100644 --- a/tools/apidoc/pom.xml +++ b/tools/apidoc/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/tools/checkstyle/pom.xml b/tools/checkstyle/pom.xml index a63f62c9aa1b..b5d2b2bb1ccc 100644 --- a/tools/checkstyle/pom.xml +++ b/tools/checkstyle/pom.xml @@ -22,7 +22,7 @@ Apache CloudStack Developer Tools - Checkstyle Configuration org.apache.cloudstack checkstyle - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT UTF-8 diff --git a/tools/devcloud-kvm/pom.xml b/tools/devcloud-kvm/pom.xml index 9b9c4bd9356b..ee0c908afbd8 100644 --- a/tools/devcloud-kvm/pom.xml +++ b/tools/devcloud-kvm/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/tools/devcloud4/pom.xml b/tools/devcloud4/pom.xml index 0dcc0dfc7f92..65d6bb17a467 100644 --- a/tools/devcloud4/pom.xml +++ b/tools/devcloud4/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/tools/marvin/pom.xml b/tools/marvin/pom.xml index 494d13c0e1d3..5e2e53c87b3b 100644 --- a/tools/marvin/pom.xml +++ b/tools/marvin/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 897bd3196729..666c4a275e3f 100644 --- a/tools/marvin/setup.py +++ b/tools/marvin/setup.py @@ -27,7 +27,7 @@ raise RuntimeError("python setuptools is required to build Marvin") -VERSION = "4.14.0.0-SNAPSHOT" +VERSION = "4.15.0.0-SNAPSHOT" setup(name="Marvin", version=VERSION, diff --git a/tools/pom.xml b/tools/pom.xml index c7763cba89e2..bb3492da1b5b 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/usage/pom.xml b/usage/pom.xml index b985761675d8..d3b83f6cbc2c 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT diff --git a/utils/pom.xml b/utils/pom.xml index 046e14a77193..163232e80629 100755 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT ../pom.xml diff --git a/vmware-base/pom.xml b/vmware-base/pom.xml index 38c63cdffa46..c84580f90b4b 100644 --- a/vmware-base/pom.xml +++ b/vmware-base/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.14.0.0-SNAPSHOT + 4.15.0.0-SNAPSHOT From c9b97460dd49f491660e8f643c120057bcd05cbc Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 5 Jun 2020 18:47:25 +0530 Subject: [PATCH 02/30] Modified list responses for projectAccounts and invitations to include userid --- .../apache/cloudstack/acl/ProjectRole.java | 15 ----- .../user/account/AddUserToProjectCmd.java | 4 +- .../user/account/ListProjectAccountsCmd.java | 6 ++ .../project/ListProjectInvitationsCmd.java | 8 +++ .../api/response/ProjectAccountResponse.java | 9 ++- .../response/ProjectInvitationResponse.java | 9 ++- .../com/cloud/projects/ProjectAccountVO.java | 6 +- .../dao/ProjectRolePermissionsDaoImpl.java | 5 +- .../META-INF/db/schema-41400to41500.sql | 62 ++++++++++++++++++- .../acl/ProjectRoleBasedApiAccessChecker.java | 3 +- .../main/java/com/cloud/api/ApiDBUtils.java | 4 ++ .../com/cloud/api/query/QueryManagerImpl.java | 10 +++ .../cloud/api/query/ViewResponseHelper.java | 7 ++- .../query/dao/ProjectAccountJoinDaoImpl.java | 1 + .../dao/ProjectInvitationJoinDaoImpl.java | 6 +- .../api/query/vo/ProjectAccountJoinVO.java | 10 +++ .../api/query/vo/ProjectInvitationJoinVO.java | 5 ++ .../cloud/projects/ProjectManagerImpl.java | 5 +- 18 files changed, 144 insertions(+), 31 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java b/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java index a64011294291..601c1684d264 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.java @@ -20,22 +20,7 @@ import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -import com.google.common.base.Enums; -import com.google.common.base.Strings; - public interface ProjectRole extends RoleEntity, InternalIdentity, Identity { Long getProjectId(); - - public enum ProjectRoleType { - Admin, Regular; - - public static ProjectRoleType fromString(final String name) { - if (!Strings.isNullOrEmpty(name) - && Enums.getIfPresent(RoleType.class, name).isPresent()) { - return ProjectRoleType.valueOf(name); - } - throw new IllegalStateException("Illegal ProjectRoleType name provided"); - } - } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 5c5ceba26f0e..144847c98fcf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -59,7 +59,7 @@ public class AddUserToProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "email ID of user to which invitation to the project is going to be sent") private String email; - @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = BaseCmd.CommandType.UUID, required = true, entityType = ProjectRoleResponse.class, + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = BaseCmd.CommandType.UUID, entityType = ProjectRoleResponse.class, description = "ID of the project role", validations = {ApiArgValidator.PositiveNumber}) private Long projectRoleId; @@ -133,7 +133,7 @@ private void validateInput() { if (getUserId() < 1L) { throw new InvalidParameterValueException("Invalid User ID provided"); } - if (getProjectRoleId() < 1L) { + if (projectRoleId != null && projectRoleId < 1L) { throw new InvalidParameterValueException("Invalid Project role ID provided"); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java index 3fcb1989fa56..016334636925 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.account; +import org.apache.cloudstack.api.response.UserResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -45,6 +46,9 @@ public class ListProjectAccountsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "list accounts of the project by account name") private String accountName; + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "list invitation by user ID") + private Long userId; + @Parameter(name = ApiConstants.ROLE, type = CommandType.STRING, description = "list accounts of the project by role") private String role; @@ -64,6 +68,8 @@ public String getRole() { return role; } + public Long getUserId() { return userId; } + @Override public String getCommandName() { return s_name; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectInvitationsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectInvitationsCmd.java index f34f4226b0ec..4a85c9d2bcda 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectInvitationsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectInvitationsCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.project; +import org.apache.cloudstack.api.response.UserResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -53,6 +54,9 @@ public class ListProjectInvitationsCmd extends BaseListAccountResourcesCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectInvitationResponse.class, description = "list invitations by id") private Long id; + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "list invitation by user ID") + private Long userId; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -72,6 +76,10 @@ public Long getId() { return id; } + public Long getUserId() { + return userId; + } + @Override public String getCommandName() { return s_name; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java index 7afad59096b8..f01827e2e631 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java @@ -18,14 +18,13 @@ import java.util.List; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import com.cloud.projects.ProjectAccount; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = ProjectAccount.class) @SuppressWarnings("unused") @@ -50,6 +49,10 @@ public class ProjectAccountResponse extends BaseResponse implements ControlledVi @Param(description = "account type (admin, domain-admin, user)") private Short accountType; + @SerializedName(ApiConstants.USER_ID) + @Param(description = "aId of the user") + private String userId; + @SerializedName(ApiConstants.ROLE) @Param(description = "account role in the project (regular,owner)") private String role; @@ -99,6 +102,8 @@ public void setDomainName(String domainName) { this.domainName = domainName; } + public void setUserId(String userId) { this.userId = userId; } + public void setUsers(List users) { this.users = users; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java index 8768df71b870..4462ea915680 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java @@ -16,14 +16,13 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import com.cloud.projects.ProjectInvitation; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = ProjectInvitation.class) @SuppressWarnings("unused") @@ -44,6 +43,10 @@ public class ProjectInvitationResponse extends BaseResponse implements Controlle @Param(description = "the domain id the project belongs to") private String domainId; + @SerializedName(ApiConstants.USER_ID) + @Param(description = "the User ID") + private String userId; + @SerializedName(ApiConstants.DOMAIN) @Param(description = "the domain name where the project belongs to") private String domainName; @@ -89,6 +92,8 @@ public void setAccountName(String accountName) { this.accountName = accountName; } + public void setUserId(String userId) { this.userId = userId; } + public void setInvitationState(String invitationState) { this.invitationState = invitationState; } diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java index 5a78c5a69eb6..4710a815f978 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java @@ -67,7 +67,11 @@ protected ProjectAccountVO() { public ProjectAccountVO(Project project, long accountId, Role accountRole, Long userId, Long projectRoleId) { this.accountId = accountId; - this.accountRole = accountRole; + if (accountRole != null) { + this.accountRole = accountRole; + } else { + this.accountRole = Role.Regular; + } this.projectId = project.getId(); this.projectAccountId = project.getProjectAccountId(); this.userId = userId; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java index b22789884bd5..547c916b41e9 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; import org.apache.cloudstack.acl.ProjectRolePermissionVO; +import org.apache.log4j.Logger; import com.cloud.utils.db.Attribute; import com.cloud.utils.db.Filter; @@ -41,6 +42,7 @@ public class ProjectRolePermissionsDaoImpl extends GenericDaoBase implements ProjectRolePermissionsDao{ + private static final Logger LOGGER = Logger.getLogger(ProjectRolePermissionsDaoImpl.class); private final SearchBuilder ProjectRolePermissionsSearch; private Attribute sortOrderAttribute; @@ -129,8 +131,7 @@ public boolean update(ProjectRole role, ProjectRolePermission rolePermission, Pe return false; } projectRolePermissionVO.setPermission(permission); - update(rolePermission.getId(), projectRolePermissionVO); - return false; + return update(rolePermission.getId(), projectRolePermissionVO); } @Override diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index f1b5fbf9f6b5..ea735774da80 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -105,4 +105,64 @@ ALTER TABLE `cloud`.`project_invitations` ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, ADD COLUMN `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)' AFTER `domain_id`, ADD CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, - ADD UNIQUE (`project_id`, `user_id`); \ No newline at end of file + ADD UNIQUE (`project_id`, `user_id`); + +-- Alter project_invitation_view to incroporate user_id as a field +ALTER VIEW `cloud`.`project_invitation_view` AS + select + project_invitations.id, + project_invitations.uuid, + project_invitations.email, + project_invitations.created, + project_invitations.state, + projects.id project_id, + projects.uuid project_uuid, + projects.name project_name, + account.id account_id, + account.uuid account_uuid, + account.account_name, + account.type account_type, + user.id user_id, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path + from + `cloud`.`project_invitations` + left join + `cloud`.`account` ON project_invitations.account_id = account.id + left join + `cloud`.`domain` ON project_invitations.domain_id = domain.id + left join + `cloud`.`projects` ON projects.id = project_invitations.project_id + left join + `cloud`.`user` ON project_invitations.user_id = user.id; + +-- Alter project_account_view to incorporate user id +ALTER VIEW `cloud`.`project_account_view` AS + select + project_account.id, + account.id account_id, + account.uuid account_uuid, + account.account_name, + account.type account_type, + user.id user_id, + user.uuid user_uuid, + project_account.account_role, + projects.id project_id, + projects.uuid project_uuid, + projects.name project_name, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path + from + `cloud`.`project_account` + inner join + `cloud`.`account` ON project_account.account_id = account.id + inner join + `cloud`.`domain` ON account.domain_id = domain.id + inner join + `cloud`.`projects` ON projects.id = project_account.project_id + left join + `cloud`.`user` ON (project_account.user_id = user.id); diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 13f5452ed4d9..6e83a723db85 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -114,7 +114,8 @@ private boolean isPermitted(Project project, ProjectAccount projectUser, String } } } - throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account."); + + throw new UnavailableCommandException("The API " + apiCommandName + " cidrs."); } @Override diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index e6a1be6ff49a..e6b307757956 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -1142,6 +1142,10 @@ public static User findUserById(Long userId) { return s_userDao.findById(userId); } + public static UserAccountJoinVO findUserAccountById(Long id) { + return s_userAccountJoinDao.findById(id); + } + public static UserVm findUserVmById(Long vmId) { return s_userVmDao.findById(vmId); } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index e2190881074d..e9bdf052ada3 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -1525,6 +1525,7 @@ public Pair, Integer> listProjectInvitationsIntern boolean activeOnly = cmd.isActiveOnly(); Long startIndex = cmd.getStartIndex(); Long pageSizeVal = cmd.getPageSizeVal(); + Long userId = cmd.getUserId(); boolean isRecursive = cmd.isRecursive(); boolean listAll = cmd.listAll(); @@ -1553,6 +1554,10 @@ public Pair, Integer> listProjectInvitationsIntern sc.setParameters("projectId", projectId); } + if (userId != null) { + sc.setParameters("userId", userId); + } + if (state != null) { sc.setParameters("state", state); } @@ -1582,6 +1587,7 @@ public ListResponse listProjectAccounts(ListProjectAccou public Pair, Integer> listProjectAccountsInternal(ListProjectAccountsCmd cmd) { long projectId = cmd.getProjectId(); String accountName = cmd.getAccountName(); + Long userId = cmd.getUserId(); String role = cmd.getRole(); Long startIndex = cmd.getStartIndex(); Long pageSizeVal = cmd.getPageSizeVal(); @@ -1624,6 +1630,10 @@ public Pair, Integer> listProjectAccountsInternal(Lis sc.setParameters("accountName", accountName); } + if (userId != null) { + sc.setParameters("userId", userId); + } + return _projectAccountJoinDao.searchAndCount(sc, searchFilter); } diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java index f01c2c42e1b5..eec3f088dd68 100644 --- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java @@ -216,7 +216,12 @@ public static List createProjectAccountResponse(ProjectA // update user list Account caller = CallContext.current().getCallingAccount(); if (ApiDBUtils.isAdmin(caller)) { - List users = ApiDBUtils.findUserViewByAccountId(proj.getAccountId()); + List users = null; + if (proj.getUserUuid() != null) { + users = Collections.singletonList(ApiDBUtils.findUserAccountById(proj.getUserId())); + } else { + users = ApiDBUtils.findUserViewByAccountId(proj.getAccountId()); + } resp.setUsers(ViewResponseHelper.createUserResponse(users.toArray(new UserAccountJoinVO[users.size()]))); } responseList.add(resp); diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java index ad5db14d0748..1230922b9670 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java @@ -55,6 +55,7 @@ public ProjectAccountResponse newProjectAccountResponse(ProjectAccountJoinVO pro projectAccountResponse.setAccountId(proj.getAccountUuid()); projectAccountResponse.setAccountName(proj.getAccountName()); + projectAccountResponse.setUserId(proj.getUserUuid()); projectAccountResponse.setAccountType(proj.getAccountType()); projectAccountResponse.setRole(proj.getAccountRole().toString()); projectAccountResponse.setDomainId(proj.getDomainUuid()); diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDaoImpl.java index 33a35d97df7b..8e155da31b61 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDaoImpl.java @@ -57,7 +57,11 @@ public ProjectInvitationResponse newProjectInvitationResponse(ProjectInvitationJ if (invite.getAccountName() != null) { response.setAccountName(invite.getAccountName()); - } else { + } + if (invite.getUserId() != null) { + response.setUserId(invite.getUserId()); + } + if (invite.getEmail() != null) { response.setEmail(invite.getEmail()); } diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java index 4766de4faedb..8d73b4585088 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java @@ -72,6 +72,12 @@ public class ProjectAccountJoinVO extends BaseViewVO implements InternalIdentity @Column(name = "project_name") private String projectName; + @Column(name = "user_id") + private Long userId; + + @Column(name = "user_uuid") + private String userUuid; + public ProjectAccountJoinVO() { } @@ -127,4 +133,8 @@ public String getProjectUuid() { public String getProjectName() { return projectName; } + + public Long getUserId() { return userId; } + + public String getUserUuid() { return userUuid; } } diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java index 8b4918561c24..3c2d21cfdd28 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java @@ -83,6 +83,9 @@ public class ProjectInvitationJoinVO extends BaseViewVO implements ControlledVie @Column(name = "project_name") private String projectName; + @Column(name = "user_id") + private String userId; + public ProjectInvitationJoinVO() { } @@ -162,6 +165,8 @@ public String getDomainPath() { return domainPath; } + public String getUserId() { return userId; } + @Override public Class getEntityType() { return ProjectInvitation.class; diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index a30615bc8dd4..ba44dc618f37 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -695,7 +695,7 @@ public boolean addAccountToProject(long projectId, String accountName, String em ProjectRole projectRole = null; if (projectRoleId != null) { projectRole = projectRoleDao.findById(projectRoleId); - if (projectRole == null || (projectRole != null && projectRole.getProjectId() != projectId)) { + if (projectRole == null || projectRole.getProjectId() != projectId) { throw new InvalidParameterValueException("Invalid project role ID for the given project"); } } @@ -1039,7 +1039,6 @@ public boolean updateInvitation(final long projectId, String accountName, Long u expireInvitation(invite); throw new InvalidParameterValueException("Invitation is expired for account id=" + accountName + " to the project id=" + projectId); } else { - final ProjectInvitationVO inviteFinal = invite; final Long accountIdFinal = accountId; final String accountNameFinal = accountName; @@ -1071,7 +1070,7 @@ public Boolean doInTransaction(TransactionStatus status) { if (projectAccount != null) { s_logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); } else { - assignUserToProject(project, userId, finalUser.getAccountId(), finalInvite.getAccountRole(), null); + assignUserToProject(project, finalInvite.getForUserId(), finalUser.getAccountId(), finalInvite.getAccountRole(), null); } } } else { From 44aa2399ca80cf0104df00a0dbd56662f4ae98b5 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 9 Jun 2020 16:04:08 +0530 Subject: [PATCH 03/30] revert API parameter change --- .../user/project/ActivateProjectCmd.java | 2 +- .../user/project/DeleteProjectCmd.java | 2 +- .../command/user/project/ListProjectsCmd.java | 2 +- .../user/project/SuspendProjectCmd.java | 2 +- .../user/project/UpdateProjectCmd.java | 2 +- .../acl/ProjectRoleBasedApiAccessChecker.java | 2 +- .../main/java/com/cloud/api/ApiServer.java | 19 +++++++++++++++++-- 7 files changed, 23 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java index eda814c58146..58cc93ff95d3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java @@ -44,7 +44,7 @@ public class ActivateProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") private Long id; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java index 5643d04171ff..171d51712aeb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java @@ -45,7 +45,7 @@ public class DeleteProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted") private Long id; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java index 4f83d0ec4947..db77916d5667 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java @@ -48,7 +48,7 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "list projects by project ID") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "list projects by project ID") private Long id; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "list projects by name") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java index 688c8c96ecda..57f052b3e142 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java @@ -46,7 +46,7 @@ public class SuspendProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be suspended") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be suspended") private Long id; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index b0f46b7260bb..225a1c058e61 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -49,7 +49,7 @@ public class UpdateProjectCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be modified") private Long id; @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "new Admin account for the project") diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 6e83a723db85..1d515024eade 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -92,7 +92,7 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe } } // Default deny all - throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account."); + throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account in project "+project.getUuid()); } private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index 0879ace99865..e7ea380aa305 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -31,6 +31,7 @@ import java.security.Security; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -830,6 +831,13 @@ private void buildAuditTrail(final StringBuilder auditTrailSb, final String comm } } + private boolean isSpecificAPI(String commandName) { + List commands = Arrays.asList("suspendProject", "updateProject", "activateProject", "deleteProject"); + if (commands.contains(commandName)) { + return true; + } + return false; + } @Override public boolean verifyRequest(final Map requestParameters, final Long userId, InetAddress remoteAddress) throws ServerApiException { try { @@ -850,8 +858,15 @@ public boolean verifyRequest(final Map requestParameters, fina if (userId != null) { final User user = ApiDBUtils.findUserById(userId); for (Map.Entry entry: requestParameters.entrySet()) { - if (entry.getKey().equals(ApiConstants.PROJECT_ID)) { - String projectId = String.valueOf(entry.getValue()[0]); + if (entry.getKey().equals(ApiConstants.PROJECT_ID) || isSpecificAPI(commandName)) { + String projectId = null; + if (isSpecificAPI(commandName)) { + projectId = String.valueOf(requestParameters.entrySet().stream() + .filter(e -> e.getKey().equals(ApiConstants.ID)) + .map(Map.Entry::getValue).findFirst().get()[0]); + } else { + projectId = String.valueOf(entry.getValue()[0]); + } Project project = projectDao.findByUuid(projectId); if (project != null) { CallContext.current().setProject(project); From 6e3af3cda0c3b9b1c20a307440efda13dbaa9c17 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 11 Jun 2020 09:31:30 +0530 Subject: [PATCH 04/30] changes for backward compatability --- .../com/cloud/projects/ProjectService.java | 3 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/project/UpdateProjectCmd.java | 25 ++++++- .../projects/dao/ProjectAccountDaoImpl.java | 23 ++++-- .../META-INF/db/schema-41400to41500.sql | 34 +++++++-- .../java/com/cloud/acl/DomainChecker.java | 1 - .../com/cloud/api/query/QueryManagerImpl.java | 3 + .../cloud/projects/ProjectManagerImpl.java | 73 ++++++++++++++++++- .../acl/ProjectRoleManagerImpl.java | 1 - .../projects/MockProjectManagerImpl.java | 6 +- 10 files changed, 147 insertions(+), 23 deletions(-) diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index ed3f2ada4c89..20117cf385ec 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -78,7 +78,7 @@ public interface ProjectService { Project findByNameAndDomainId(String name, long domainId); - //Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; + Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException; @@ -101,4 +101,5 @@ public interface ProjectService { Project findByProjectAccountIdIncludingRemoved(long projectAccountId); boolean addUserToProject(Long projectId, Long userId, String email, Long projectRoleId, Role projectRole); + } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index fc01d91762b5..19a1da3dff8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -347,6 +347,7 @@ public class ApiConstants { public static final String STORAGE_POLICY = "storagepolicy"; public static final String STORAGE_MOTION_ENABLED = "storagemotionenabled"; public static final String STORAGE_CAPABILITIES = "storagecapabilities"; + public static final String SWAP_OWNER = "swapowner"; public static final String SYSTEM_VM_TYPE = "systemvmtype"; public static final String TAGS = "tags"; public static final String TARGET_IQN = "targetiqn"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index 225a1c058e61..1d1b313900f1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -67,9 +67,12 @@ public class UpdateProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account owning a project") private Long accountId; - @Parameter(name = ApiConstants.ROLE_TYPE, type = CommandType.STRING, required = true, description = "Account level role to be assigned to the user/account : Admin/Regular") + @Parameter(name = ApiConstants.ROLE_TYPE, type = CommandType.STRING, description = "Account level role to be assigned to the user/account : Admin/Regular") private String roleType; + @Parameter(name = ApiConstants.SWAP_OWNER, type = CommandType.BOOLEAN, description = "True by default; when true, makes provided account the owner") + private Boolean swapOwner; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -106,7 +109,10 @@ public ProjectAccount.Role getRoleType(String role) { } public ProjectAccount.Role getAccountRole() { - return getRoleType(roleType); + if (roleType != null) { + return getRoleType(roleType); + } + return ProjectAccount.Role.Regular; } public Long getAccountId() { @@ -118,6 +124,14 @@ public String getCommandName() { return s_name; } + public Boolean isSwapOwner() { + if (swapOwner != null) { + return swapOwner; + } else { + return true; + } + } + @Override public long getEntityOwnerId() { Project project = _projectService.getProject(id); @@ -155,7 +169,12 @@ public void execute() throws ResourceAllocationException { } } - Project project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountId(), getDomainId(), getAccountRole()); + Project project = null; + if (isSwapOwner()) { + project = _projectService.updateProject(getId(), getDisplayText(), getAccountName()); + } else { + project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountId(), getDomainId(), getAccountRole()); + } if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); response.setResponseName(getCommandName()); diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java index 064b9adfb86d..e7df24f4e1cf 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java @@ -33,8 +33,9 @@ @Component public class ProjectAccountDaoImpl extends GenericDaoBase implements ProjectAccountDao { protected final SearchBuilder AllFieldsSearch; + protected final SearchBuilder ProjectAccountSearch; final GenericSearchBuilder AdminSearch; - final GenericSearchBuilder ProjectAccountSearch; + final GenericSearchBuilder ProjectAccountsSearch; final GenericSearchBuilder CountByRoleSearch; public static final Logger s_logger = Logger.getLogger(ProjectAccountDaoImpl.class.getName()); @@ -49,16 +50,23 @@ protected ProjectAccountDaoImpl() { AllFieldsSearch.and("projectRoleId", AllFieldsSearch.entity().getProjectRoleId(), Op.EQ); AllFieldsSearch.done(); + ProjectAccountSearch = createSearchBuilder(); + ProjectAccountSearch.and("projectId", ProjectAccountSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectAccountSearch.and("accountId", ProjectAccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + ProjectAccountSearch.and("userId", ProjectAccountSearch.entity().getUserId(), Op.NULL); + ProjectAccountSearch.done(); + + AdminSearch = createSearchBuilder(Long.class); AdminSearch.selectFields(AdminSearch.entity().getProjectId()); AdminSearch.and("role", AdminSearch.entity().getAccountRole(), Op.EQ); AdminSearch.and("accountId", AdminSearch.entity().getAccountId(), Op.EQ); AdminSearch.done(); - ProjectAccountSearch = createSearchBuilder(Long.class); - ProjectAccountSearch.selectFields(ProjectAccountSearch.entity().getProjectAccountId()); - ProjectAccountSearch.and("accountId", ProjectAccountSearch.entity().getAccountId(), Op.EQ); - ProjectAccountSearch.done(); + ProjectAccountsSearch = createSearchBuilder(Long.class); + ProjectAccountsSearch.selectFields(ProjectAccountsSearch.entity().getProjectAccountId()); + ProjectAccountsSearch.and("accountId", ProjectAccountsSearch.entity().getAccountId(), Op.EQ); + ProjectAccountsSearch.done(); CountByRoleSearch = createSearchBuilder(Long.class); CountByRoleSearch.select(null, Func.COUNT, CountByRoleSearch.entity().getId()); @@ -94,10 +102,9 @@ public List listByProjectId(long projectId) { @Override public ProjectAccountVO findByProjectIdAccountId(long projectId, long accountId) { - SearchCriteria sc = AllFieldsSearch.create(); + SearchCriteria sc = ProjectAccountSearch.create(); sc.setParameters("projectId", projectId); sc.setParameters("accountId", accountId); - return findOneBy(sc); } @@ -140,7 +147,7 @@ public boolean canModifyProjectAccount(long accountId, long projectAccountId) { @Override public List listPermittedAccountIds(long accountId) { - SearchCriteria sc = ProjectAccountSearch.create(); + SearchCriteria sc = ProjectAccountsSearch.create(); sc.setParameters("accountId", accountId); return customSearch(sc, null); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index ea735774da80..235986e8ec05 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -92,13 +92,33 @@ CREATE TABLE IF NOT EXISTS `cloud`.`project_role_permissions` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Alter project accounts table to include user_id and project_role_id for role based users in projects -ALTER TABLE `cloud`.`project_account` - ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, - ADD CONSTRAINT `fk_project_account__user_id` FOREIGN KEY `fk_project_account__user_id`(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, - ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `project_account_id`, - ADD CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), - -- DROP INDEX `account_id`, - ADD UNIQUE (`user_id`, `account_id`, `project_id`); +DROP TABLE IF EXISTS `cloud`.`project_account`; +CREATE TABLE `cloud`.`project_account` ( + `id` bigint unsigned NOT NULL auto_increment, + `account_id` bigint unsigned NOT NULL COMMENT'account id', + `user_id` bigint unsigned COMMENT 'ID of user to be added to the project', + `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)', + `project_id` bigint unsigned NOT NULL COMMENT 'project id', + `project_account_id` bigint unsigned NOT NULL, + `project_role_id` bigint unsigned COMMENT 'Project role id', + `created` datetime COMMENT 'date created', + PRIMARY KEY (`id`), + CONSTRAINT `fk_project_account__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_account__user_id` FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_account__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_account__project_account_id` FOREIGN KEY(`project_account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), + UNIQUE (`account_id`, `user_id`, `project_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +--ALTER TABLE `cloud`.`project_account` +-- ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, +-- ADD CONSTRAINT `fk_project_account__user_id` FOREIGN KEY `fk_project_account__user_id`(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, +-- ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `project_account_id`, +-- ADD CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), +-- DROP FOREIGN KEY fk_project_account__account_id, +-- DROP INDEX `account_id`, +-- ADD UNIQUE (`user_id`, `account_id`, `project_id`), +-- ADD CONSTRAINT `fk_project_account__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE; -- Alter project invitations table to include user_id for invites sent to specific users of an account ALTER TABLE `cloud`.`project_invitations` diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 585ced55c61d..80cdf2345360 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -122,7 +122,6 @@ public boolean checkAccess(User user, Domain domain) throws PermissionDeniedExce public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType) throws PermissionDeniedException { if (entity instanceof VirtualMachineTemplate) { - VirtualMachineTemplate template = (VirtualMachineTemplate)entity; Account owner = _accountDao.findById(template.getAccountId()); // validate that the template is usable by the account diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index e9bdf052ada3..d2aa507535ed 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -1618,6 +1618,9 @@ public Pair, Integer> listProjectAccountsInternal(Lis sb.and("accountName", sb.entity().getAccountName(), Op.EQ); } + if (userId != null) { + sb.and("userId", sb.entity().getUserId(), Op.EQ); + } SearchCriteria sc = sb.create(); sc.setParameters("projectId", projectId); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index ba44dc618f37..44412498e3d6 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -569,7 +569,78 @@ private void updateProjectAccount(ProjectAccountVO futureOwner, Role newAccRole, _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(accountId), ResourceType.project); futureOwner.setAccountRole(newAccRole); _projectAccountDao.update(futureOwner.getId(), futureOwner); - _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project); + if (newAccRole != null && Role.Admin == newAccRole) { + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project); + } else { + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project); + } + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true) + public Project updateProject(final long projectId, final String displayText, final String newOwnerName) throws ResourceAllocationException { + Account caller = CallContext.current().getCallingAccount(); + + //check that the project exists + final ProjectVO project = getProject(projectId); + + if (project == null) { + throw new InvalidParameterValueException("Unable to find the project id=" + projectId); + } + + //verify permissions + _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); + + Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { + if (displayText != null) { + project.setDisplayText(displayText); + _projectDao.update(projectId, project); + } + + if (newOwnerName != null) { + //check that the new owner exists + Account futureOwnerAccount = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); + if (futureOwnerAccount == null) { + throw new InvalidParameterValueException("Unable to find account name=" + newOwnerName + " in domain id=" + project.getDomainId()); + } + Account currentOwnerAccount = getProjectOwner(projectId); + if (currentOwnerAccount == null) { + s_logger.error("Unable to find the current owner for the project id=" + projectId); + throw new InvalidParameterValueException("Unable to find the current owner for the project id=" + projectId); + } + if (currentOwnerAccount.getId() != futureOwnerAccount.getId()) { + ProjectAccountVO futureOwner = _projectAccountDao.findByProjectIdAccountId(projectId, futureOwnerAccount.getAccountId()); + if (futureOwner == null) { + throw new InvalidParameterValueException("Account " + newOwnerName + + " doesn't belong to the project. Add it to the project first and then change the project's ownership"); + } + + //do resource limit check + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(futureOwnerAccount.getId()), ResourceType.project); + + //unset the role for the old owner + ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId()); + currentOwner.setAccountRole(Role.Regular); + _projectAccountDao.update(currentOwner.getId(), currentOwner); + _resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project); + + //set new owner + futureOwner.setAccountRole(Role.Admin); + _projectAccountDao.update(futureOwner.getId(), futureOwner); + _resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project); + + } else { + s_logger.trace("Future owner " + newOwnerName + "is already the owner of the project id=" + projectId); + } + } + } + }); + + return _projectDao.findById(projectId); + } @Override diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index 42f6867939f1..d6a26f8c4722 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -100,7 +100,6 @@ private void checkAccess(Long projectId) { throw new PermissionDeniedException("User/Account not part of project"); } } - // if (ProjectAccount.Role.Admin != projectAccount.getAccountRole()) { if (ProjectAccount.Role.Admin != projectAccount.getAccountRole()) { throw new PermissionDeniedException("User unauthorized to perform operation in the project"); } diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index 53e781062c87..999aa7c0e1cb 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -84,6 +84,11 @@ public Project findByNameAndDomainId(String name, long domainId) { return null; } + @Override + public Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException { + return null; + } + @Override public Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException { // TODO Auto-generated method stub @@ -220,5 +225,4 @@ public boolean addUserToProject(Long projectId, Long userId, String email, Long // TODO Auto-generated method stub return false; } - } From 7e0a64c442a1401881655ba3f44227d4554331e8 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 11 Jun 2020 11:44:05 +0530 Subject: [PATCH 05/30] restrict user invitation visibility to only that specific user --- .../dao/ProjectInvitationDaoImpl.java | 4 +++- .../acl/ProjectRoleBasedApiAccessChecker.java | 5 +++- .../com/cloud/api/query/QueryManagerImpl.java | 23 +++++++++++++++---- .../cloud/projects/ProjectManagerImpl.java | 6 ++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java index c72cd5cba183..5ee3debefb4e 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java @@ -74,7 +74,9 @@ public ProjectInvitationVO findByUserIdProjectId(long userId, long accountId, lo SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("userId", userId); sc.setParameters("accountId", accountId); - sc.setParameters("projectId", projectId); + if (projectId != -1) { + sc.setParameters("projectId", projectId); + } if (inviteState != null && inviteState.length > 0) { sc.setParameters("state", (Object[])inviteState); } diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 1d515024eade..ad043c28a315 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -92,7 +92,10 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe } } // Default deny all - throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account in project "+project.getUuid()); + if ("updateProjectInvitation".equals(apiCommandName)) { + return true; + } + throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account/user in project "+project.getUuid()); } private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index d2aa507535ed..1122aa06e8ca 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -197,6 +197,7 @@ import com.cloud.projects.ProjectVO; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; +import com.cloud.projects.dao.ProjectInvitationDao; import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceMetaDataService; import com.cloud.server.ResourceTag; @@ -224,6 +225,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.DomainManager; +import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; @@ -411,6 +413,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private RouterHealthCheckResultDao routerHealthCheckResultDao; + @Inject + private ProjectInvitationDao projectInvitationDao; + /* * (non-Javadoc) * @@ -1530,6 +1535,7 @@ public Pair, Integer> listProjectInvitationsIntern boolean listAll = cmd.listAll(); Account caller = CallContext.current().getCallingAccount(); + User callingUser = CallContext.current().getCallingUser(); List permittedAccounts = new ArrayList(); Ternary domainIdRecursiveListProject = new Ternary(domainId, isRecursive, null); @@ -1541,7 +1547,7 @@ public Pair, Integer> listProjectInvitationsIntern Filter searchFilter = new Filter(ProjectInvitationJoinVO.class, "id", true, startIndex, pageSizeVal); SearchBuilder sb = _projectInvitationJoinDao.createSearchBuilder(); _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); - + ProjectInvitation invitation = projectInvitationDao.findByUserIdProjectId(callingUser.getId(), callingUser.getAccountId(), projectId == null ? -1 : projectId); sb.and("projectId", sb.entity().getProjectId(), SearchCriteria.Op.EQ); sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("created", sb.entity().getCreated(), SearchCriteria.Op.GT); @@ -1554,7 +1560,9 @@ public Pair, Integer> listProjectInvitationsIntern sc.setParameters("projectId", projectId); } - if (userId != null) { + if (invitation != null) { + sc.setParameters("userId", invitation.getForUserId()); + } else if (userId != null) { sc.setParameters("userId", userId); } @@ -1571,7 +1579,11 @@ public Pair, Integer> listProjectInvitationsIntern sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime()) - _projectMgr.getInvitationTimeout())); } - return _projectInvitationJoinDao.searchAndCount(sc, searchFilter); + Pair, Integer> projectInvitations = _projectInvitationJoinDao.searchAndCount(sc, searchFilter); + List invitations = projectInvitations.first(); + invitations = invitations.stream().filter(invite -> invite.getUserId() == null || Long.parseLong(invite.getUserId()) == callingUser.getId()).collect(Collectors.toList()); + return new Pair<>(invitations, invitations.size()); + } @@ -1595,7 +1607,7 @@ public Pair, Integer> listProjectAccountsInternal(Lis // long projectId, String accountName, String role, Long startIndex, // Long pageSizeVal) { Account caller = CallContext.current().getCallingAccount(); - + User callingUser = CallContext.current().getCallingUser(); // check that the project exists Project project = _projectDao.findById(projectId); @@ -1605,7 +1617,8 @@ public Pair, Integer> listProjectAccountsInternal(Lis // verify permissions - only accounts belonging to the project can list // project's account - if (!_accountMgr.isAdmin(caller.getId()) && _projectAccountDao.findByProjectIdAccountId(projectId, caller.getAccountId()) == null) { + if (!_accountMgr.isAdmin(caller.getId()) && _projectAccountDao.findByProjectIdUserId(projectId, callingUser.getAccountId(), callingUser.getId()) == null && + _projectAccountDao.findByProjectIdAccountId(projectId, caller.getAccountId()) == null) { throw new PermissionDeniedException("Account " + caller + " is not authorized to list users of the project id=" + projectId); } diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 44412498e3d6..6e3c8e65d569 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -833,7 +833,8 @@ private boolean inviteUserToProject(Project project, User user, String email, Ro private boolean isTheOnlyProjectOwnerOrOneself(Long projectId, ProjectAccount projectAccount, Account caller) { List projectOwners = _projectAccountDao.getProjectOwners(projectId); if ((projectOwners.size() == 1 && projectOwners.get(0).getAccountId() == projectAccount.getAccountId() - && projectAccount.getAccountRole() == Role.Admin ) || (projectAccount.getAccountId() == caller.getAccountId())) { + && projectAccount.getAccountRole() == Role.Admin )) { + // || (projectAccount.getAccountId() == caller.getAccountId()) return true; } return false; @@ -1072,7 +1073,6 @@ public boolean updateInvitation(final long projectId, String accountName, Long u if (project == null) { throw new InvalidParameterValueException("Unable to find the project id=" + projectId); } - CallContext.current().setProject(project); if (accountName != null) { //check that account-to-remove exists @@ -1083,7 +1083,6 @@ public boolean updateInvitation(final long projectId, String accountName, Long u //verify permissions _accountMgr.checkAccess(caller, null, true, account); - accountId = account.getId(); } else { user = userDao.findById(userId); @@ -1096,6 +1095,7 @@ public boolean updateInvitation(final long projectId, String accountName, Long u //check that invitation exists ProjectInvitationVO invite = null; if (token == null) { + if (accountName != null) { invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); } else { From b2730dd5d6963da17eb82998677ce54d9332cda2 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 18 Jun 2020 12:27:45 +0530 Subject: [PATCH 06/30] Added additional response params - listProjectAccounts + schema changes + Marvin tests --- .../cloudstack/api/ApiServerService.java | 2 +- .../user/account/AddAccountToProjectCmd.java | 2 +- .../user/account/AddUserToProjectCmd.java | 3 + .../user/account/ListProjectAccountsCmd.java | 13 +- .../api/response/ProjectAccountResponse.java | 10 +- .../api/response/ProjectResponse.java | 3 +- .../dao/ProjectInvitationDaoImpl.java | 9 +- .../META-INF/db/schema-41400to41500.sql | 41 ++-- .../acl/ProjectRoleBasedApiAccessChecker.java | 2 +- .../java/com/cloud/acl/DomainChecker.java | 36 --- .../main/java/com/cloud/api/ApiServer.java | 25 -- .../main/java/com/cloud/api/ApiServlet.java | 41 +++- .../com/cloud/api/query/QueryManagerImpl.java | 6 +- .../query/dao/ProjectAccountJoinDaoImpl.java | 2 +- .../api/query/vo/ProjectAccountJoinVO.java | 14 ++ .../cloud/projects/ProjectManagerImpl.java | 11 +- .../acl/ProjectRoleManagerImpl.java | 2 - ...est_enable_role_based_users_in_projects.py | 230 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 127 +++++++++- .../main/java/com/cloud/utils/HttpUtils.java | 7 +- 20 files changed, 485 insertions(+), 101 deletions(-) create mode 100644 test/integration/smoke/test_enable_role_based_users_in_projects.py diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index 382b48a5e026..d1584d550b39 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -39,7 +39,7 @@ public ResponseObject loginUser(HttpSession session, String username, String pas public String getSerializedApiError(ServerApiException ex, Map apiCommandParams, String responseType); - public String handleRequest(Map params, String responseType, StringBuilder auditTrailSb) throws ServerApiException; + public String handleRequest(Map params, String responseType, StringBuilder auditTrailSb) throws ServerApiException; public Class getCmdClass(String cmdName); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java index b2e0adc38c7f..26506b4b0d2c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java @@ -68,7 +68,7 @@ public class AddAccountToProjectCmd extends BaseAsyncCmd { private Long projectRoleId; @Parameter(name = ApiConstants.ROLE_TYPE, type = BaseCmd.CommandType.STRING, - description = "Project role type to be assigned to the user - Admin/Regular") + description = "Project role type to be assigned to the user - Admin/Regular; default: Regular") private String roleType; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 144847c98fcf..ec58336b2fc0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -124,6 +124,9 @@ public void execute() { } private void validateInput() { + if (email == null && userId == null) { + throw new InvalidParameterValueException("Must specify atleast userID"); + } if (email != null && userId == null) { throw new InvalidParameterValueException("Must specify userID for given email ID"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java index 016334636925..bc10e4b6e4f3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java @@ -16,9 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.account; -import org.apache.cloudstack.api.response.UserResponse; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListCmd; @@ -26,6 +23,9 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ProjectRoleResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.log4j.Logger; import com.cloud.user.Account; @@ -52,6 +52,9 @@ public class ListProjectAccountsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ROLE, type = CommandType.STRING, description = "list accounts of the project by role") private String role; + @Parameter(name = ApiConstants.PROJECT_ROLE_ID, type = CommandType.UUID, entityType = ProjectRoleResponse.class, description = "list accounts of the project by project role id") + private Long projectRoleId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +73,10 @@ public String getRole() { public Long getUserId() { return userId; } + public Long getProjectRoleId() { + return projectRoleId; + } + @Override public String getCommandName() { return s_name; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java index f01827e2e631..1070cb3274b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java @@ -50,9 +50,13 @@ public class ProjectAccountResponse extends BaseResponse implements ControlledVi private Short accountType; @SerializedName(ApiConstants.USER_ID) - @Param(description = "aId of the user") + @Param(description = "Id of the user") private String userId; + @SerializedName(ApiConstants.PROJECT_ROLE_ID) + @Param(description = "Id of the project role associated with the account/user") + private String projectRoleId; + @SerializedName(ApiConstants.ROLE) @Param(description = "account role in the project (regular,owner)") private String role; @@ -104,6 +108,10 @@ public void setDomainName(String domainName) { public void setUserId(String userId) { this.userId = userId; } + public void setProjectRoleId(String projectRoleId) { + this.projectRoleId = projectRoleId; + } + public void setUsers(List users) { this.users = users; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index 8bfa6d94b63c..9d9935f85442 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -19,14 +19,13 @@ import java.util.ArrayList; import java.util.List; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import com.cloud.projects.Project; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = Project.class) public class ProjectResponse extends BaseResponse implements ResourceLimitAndCountResponse { diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java index 5ee3debefb4e..f8d153740ab7 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java @@ -34,6 +34,7 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase AllFieldsSearch; protected final SearchBuilder InactiveSearch; + protected final SearchBuilder ProjectAccountInviteSearch; protected ProjectInvitationDaoImpl() { AllFieldsSearch = createSearchBuilder(); @@ -48,6 +49,12 @@ protected ProjectInvitationDaoImpl() { AllFieldsSearch.and("id", AllFieldsSearch.entity().getId(), SearchCriteria.Op.EQ); AllFieldsSearch.done(); + ProjectAccountInviteSearch = createSearchBuilder(); + ProjectAccountInviteSearch.and("accountId", ProjectAccountInviteSearch.entity().getForAccountId(), SearchCriteria.Op.EQ); + ProjectAccountInviteSearch.and("projectId", ProjectAccountInviteSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectAccountInviteSearch.and("userId", ProjectAccountInviteSearch.entity().getForUserId(), SearchCriteria.Op.NULL); + ProjectAccountInviteSearch.done(); + InactiveSearch = createSearchBuilder(); InactiveSearch.and("id", InactiveSearch.entity().getId(), SearchCriteria.Op.EQ); InactiveSearch.and("accountId", InactiveSearch.entity().getForAccountId(), SearchCriteria.Op.EQ); @@ -59,7 +66,7 @@ protected ProjectInvitationDaoImpl() { @Override public ProjectInvitationVO findByAccountIdProjectId(long accountId, long projectId, State... inviteState) { - SearchCriteria sc = AllFieldsSearch.create(); + SearchCriteria sc = ProjectAccountInviteSearch.create(); sc.setParameters("accountId", accountId); sc.setParameters("projectId", projectId); if (inviteState != null && inviteState.length > 0) { diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index 235986e8ec05..b5bb3579a531 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -110,22 +110,31 @@ CREATE TABLE `cloud`.`project_account` ( CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), UNIQUE (`account_id`, `user_id`, `project_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ---ALTER TABLE `cloud`.`project_account` --- ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, --- ADD CONSTRAINT `fk_project_account__user_id` FOREIGN KEY `fk_project_account__user_id`(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, --- ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `project_account_id`, --- ADD CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), --- DROP FOREIGN KEY fk_project_account__account_id, --- DROP INDEX `account_id`, --- ADD UNIQUE (`user_id`, `account_id`, `project_id`), --- ADD CONSTRAINT `fk_project_account__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE; -- Alter project invitations table to include user_id for invites sent to specific users of an account -ALTER TABLE `cloud`.`project_invitations` - ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, - ADD COLUMN `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)' AFTER `domain_id`, - ADD CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, - ADD UNIQUE (`project_id`, `user_id`); +DROP TABLE IF EXISTS `cloud`.`project_invitations`; +CREATE TABLE `cloud`.`project_invitations` ( + `id` bigint unsigned NOT NULL auto_increment, + `uuid` varchar(40), + `project_id` bigint unsigned NOT NULL COMMENT 'project id', + `account_id` bigint unsigned COMMENT 'account id', + `user_id` bigint unsigned COMMENT 'ID of user to be added to the project', + `domain_id` bigint unsigned COMMENT 'domain id', + `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)', + `email` varchar(255) COMMENT 'email', + `token` varchar(255) COMMENT 'token', + `state` varchar(255) NOT NULL DEFAULT 'Pending' COMMENT 'the state of the invitation', + `created` datetime COMMENT 'date created', + PRIMARY KEY (`id`), + CONSTRAINT `fk_project_invitations__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_invitations__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_project_invitations__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE, + UNIQUE (`project_id`, `account_id`,`user_id`), + UNIQUE (`project_id`, `email`), + UNIQUE (`project_id`, `token`), + CONSTRAINT `uc_project_invitations__uuid` UNIQUE (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Alter project_invitation_view to incroporate user_id as a field ALTER VIEW `cloud`.`project_invitation_view` AS @@ -169,6 +178,8 @@ ALTER VIEW `cloud`.`project_account_view` AS user.id user_id, user.uuid user_uuid, project_account.account_role, + project_role.id project_role_id, + project_role.uuid project_role_uuid, projects.id project_id, projects.uuid project_uuid, projects.name project_name, @@ -185,4 +196,6 @@ ALTER VIEW `cloud`.`project_account_view` AS inner join `cloud`.`projects` ON projects.id = project_account.project_id left join + `cloud`.`project_role` ON project_account.project_role_id = project_role.id + left join `cloud`.`user` ON (project_account.user_id = user.id); diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index ad043c28a315..a01f809d73f6 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -118,7 +118,7 @@ private boolean isPermitted(Project project, ProjectAccount projectUser, String } } - throw new UnavailableCommandException("The API " + apiCommandName + " cidrs."); + return true; } @Override diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 80cdf2345360..f864b023c89d 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -23,7 +23,6 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; -import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -42,9 +41,6 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; -import com.cloud.projects.Project; -import com.cloud.projects.ProjectAccount; -import com.cloud.projects.ProjectAccountVO; import com.cloud.projects.ProjectManager; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; @@ -134,9 +130,6 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a return true; } - // TODO: verify?? - checkProjectAccess(owner); - // since the current account is not the owner of the template, check the launch permissions table to see if the // account can launch a VM from this template LaunchPermissionVO permission = _launchPermissionDao.findByTemplateAndAccount(template.getId(), caller.getId()); @@ -421,35 +414,6 @@ else if (_accountService.isDomainAdmin(account.getId())) { return false; } - private void checkProjectAccess(Account caller) { - User user = CallContext.current().getCallingUser(); - Project project = CallContext.current().getProject(); - if (project == null) { - return; - } - ProjectAccountVO projectAccountVO = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); - if (projectAccountVO != null) { - if (isProjectAdmin(projectAccountVO)) { - return; - } - throw new PermissionDeniedException(String.format("User %s of account %s doesn't have permission in project %s", user.getId(), caller.getId(), project.getId())); - } - projectAccountVO = _projectAccountDao.findByProjectIdAccountId(project.getId(), caller.getAccountId()); - if (projectAccountVO != null) { - if (isProjectAdmin(projectAccountVO)) { - return; - } - throw new PermissionDeniedException(String.format("Account %s doesn't have permission in project %s", caller.getId(), project.getId())); - } - } - - private boolean isProjectAdmin(ProjectAccountVO projectAccount) { - if (projectAccount.getAccountRole() != ProjectAccount.Role.Admin) { - throw new PermissionDeniedException("Current user is not permitted to perform the operation in the project"); - } - return true; - } - @Override public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) throws PermissionDeniedException { diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index e7ea380aa305..cf0891fb606b 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -31,7 +31,6 @@ import java.security.Security; import java.text.ParseException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -157,7 +156,6 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnavailableCommandException; -import com.cloud.projects.Project; import com.cloud.projects.dao.ProjectDao; import com.cloud.storage.VolumeApiService; import com.cloud.user.Account; @@ -831,13 +829,6 @@ private void buildAuditTrail(final StringBuilder auditTrailSb, final String comm } } - private boolean isSpecificAPI(String commandName) { - List commands = Arrays.asList("suspendProject", "updateProject", "activateProject", "deleteProject"); - if (commands.contains(commandName)) { - return true; - } - return false; - } @Override public boolean verifyRequest(final Map requestParameters, final Long userId, InetAddress remoteAddress) throws ServerApiException { try { @@ -857,22 +848,6 @@ public boolean verifyRequest(final Map requestParameters, fina // if userId not null, that mean that user is logged in if (userId != null) { final User user = ApiDBUtils.findUserById(userId); - for (Map.Entry entry: requestParameters.entrySet()) { - if (entry.getKey().equals(ApiConstants.PROJECT_ID) || isSpecificAPI(commandName)) { - String projectId = null; - if (isSpecificAPI(commandName)) { - projectId = String.valueOf(requestParameters.entrySet().stream() - .filter(e -> e.getKey().equals(ApiConstants.ID)) - .map(Map.Entry::getValue).findFirst().get()[0]); - } else { - projectId = String.valueOf(entry.getValue()[0]); - } - Project project = projectDao.findByUuid(projectId); - if (project != null) { - CallContext.current().setProject(project); - } - } - } return commandAvailable(remoteAddress, commandName, user); } else { // check against every available command to see if the command exists or not diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index 4002ff8d99b1..9eb12ae9055e 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -47,6 +47,8 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import com.cloud.projects.Project; +import com.cloud.projects.dao.ProjectDao; import com.cloud.user.Account; import com.cloud.user.AccountService; import com.cloud.user.User; @@ -75,6 +77,8 @@ public class ApiServlet extends HttpServlet { ManagedContext managedContext; @Inject APIAuthenticationManager authManager; + @Inject + private ProjectDao projectDao; public ApiServlet() { } @@ -202,6 +206,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp } } session = req.getSession(true); + if (ApiServer.EnableSecureSessionCookie.value()) { resp.setHeader("SET-COOKIE", String.format("JSESSIONID=%s;Secure;HttpOnly;Path=/client", session.getId())); if (s_logger.isDebugEnabled()) { @@ -300,7 +305,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp } else { CallContext.register(accountMgr.getSystemUser(), accountMgr.getSystemAccount()); } - + setProjectContext(params); if (apiServer.verifyRequest(params, userId, remoteAddress)) { auditTrailSb.insert(0, "(userId=" + CallContext.current().getCallingUserId() + " accountId=" + CallContext.current().getCallingAccount().getId() + " sessionId=" + (session != null ? session.getId() : null) + ")"); @@ -342,6 +347,40 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp } } + private void setProjectContext(Map requestParameters) { + final String[] command = (String[])requestParameters.get(ApiConstants.COMMAND); + if (command == null) { + s_logger.info("missing command, ignoring request..."); + return; + } + + final String commandName = command[0]; + for (Map.Entry entry: requestParameters.entrySet()) { + if (entry.getKey().equals(ApiConstants.PROJECT_ID) || isSpecificAPI(commandName)) { + String projectId = null; + if (isSpecificAPI(commandName)) { + projectId = String.valueOf(requestParameters.entrySet().stream() + .filter(e -> e.getKey().equals(ApiConstants.ID)) + .map(Map.Entry::getValue).findFirst().get()[0]); + } else { + projectId = String.valueOf(entry.getValue()[0]); + } + Project project = projectDao.findByUuid(projectId); + if (project != null) { + CallContext.current().setProject(project); + } + } + } + } + + private boolean isSpecificAPI(String commandName) { + List commands = Arrays.asList("suspendProject", "updateProject", "activateProject", "deleteProject"); + if (commands.contains(commandName)) { + return true; + } + return false; + } + //This method will try to get login IP of user even if servlet is behind reverseProxy or loadBalancer static InetAddress getClientAddress(final HttpServletRequest request) throws UnknownHostException { for(final String header : s_clientAddressHeaders) { diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 5545df321ed2..656c4b44255c 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -1605,7 +1605,7 @@ public Pair, Integer> listProjectAccountsInternal(Lis String role = cmd.getRole(); Long startIndex = cmd.getStartIndex(); Long pageSizeVal = cmd.getPageSizeVal(); - + Long projectRoleId = cmd.getProjectRoleId(); // long projectId, String accountName, String role, Long startIndex, // Long pageSizeVal) { Account caller = CallContext.current().getCallingAccount(); @@ -1648,6 +1648,10 @@ public Pair, Integer> listProjectAccountsInternal(Lis sc.setParameters("accountName", accountName); } + if (projectRoleId != null) { + sc.setParameters("projectRoleId", projectRoleId); + } + if (userId != null) { sc.setParameters("userId", userId); } diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java index 1230922b9670..598c5e1b3707 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java @@ -52,7 +52,7 @@ public ProjectAccountResponse newProjectAccountResponse(ProjectAccountJoinVO pro projectAccountResponse.setProjectId(proj.getProjectUuid()); projectAccountResponse.setProjectName(proj.getProjectName()); - + projectAccountResponse.setProjectRoleId(proj.getProjectRoleUuid()); projectAccountResponse.setAccountId(proj.getAccountUuid()); projectAccountResponse.setAccountName(proj.getAccountName()); projectAccountResponse.setUserId(proj.getUserUuid()); diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java index 8d73b4585088..537d0dc69830 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java @@ -75,6 +75,12 @@ public class ProjectAccountJoinVO extends BaseViewVO implements InternalIdentity @Column(name = "user_id") private Long userId; + @Column(name = "project_role_id") + private Long projectRoleId; + + @Column(name = "project_role_uuid") + private String projectRoleUuid; + @Column(name = "user_uuid") private String userUuid; @@ -136,5 +142,13 @@ public String getProjectName() { public Long getUserId() { return userId; } + public Long getProjectRoleId() { + return projectRoleId; + } + + public String getProjectRoleUuid() { + return projectRoleUuid; + } + public String getUserUuid() { return userUuid; } } diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 6e3c8e65d569..737f0858d32a 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Random; import java.util.TimeZone; @@ -243,7 +244,8 @@ public Project doInTransaction(TransactionStatus status) { Project project = _projectDao.persist(new ProjectVO(name, displayText, ownerFinal.getDomainId(), projectAccount.getId())); //assign owner to the project - assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin, (finalUser != null ? finalUser.getId() : null), null); + assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin, + Optional.ofNullable(finalUser).map(User::getId).orElse(null), null); if (project != null) { CallContext.current().setEventDetails("Project id=" + project.getId()); @@ -516,7 +518,8 @@ public boolean addUserToProject(Long projectId, Long userId, String email, Long if (userId == null) { throw new InvalidParameterValueException("User information (ID) is required to add user to the project"); } - if (assignUserToProject(project, userId, user.getAccountId(), projectRole, role != null ? role.getId() : null) != null) { + if (assignUserToProject(project, userId, user.getAccountId(), projectRole, + Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) { return true; } s_logger.warn("Failed to add user to project with id: " + projectId); @@ -777,7 +780,8 @@ public boolean addAccountToProject(long projectId, String accountName, String em if (account == null) { throw new InvalidParameterValueException("Account information is required for assigning account to the project"); } - if (assignAccountToProject(project, account.getId(), projectRoleType, null, projectRole != null ? projectRole.getId() : null) != null) { + if (assignAccountToProject(project, account.getId(), projectRoleType, null, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to add account " + accountName + " to project id=" + projectId); @@ -1260,7 +1264,6 @@ public static String generateToken(int length) { } return sb.toString(); } - class EmailInvite { private Session _smtpSession; private final String _smtpHost; diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index d6a26f8c4722..b56494f9c0a5 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -154,12 +154,10 @@ public ProjectRole findProjectRole(Long roleId, Long projectId) { LOGGER.warn(String.format("Project Role not found [id=%s]", roleId)); return null; } - Account currentAcc = getCurrentAccount(); if (!(role.getProjectId().equals(projectId))) { LOGGER.warn(String.format("Project role : %s doesn't belong to the project" + role.getName())); return null; } - return role; } diff --git a/test/integration/smoke/test_enable_role_based_users_in_projects.py b/test/integration/smoke/test_enable_role_based_users_in_projects.py new file mode 100644 index 000000000000..434ffa69575a --- /dev/null +++ b/test/integration/smoke/test_enable_role_based_users_in_projects.py @@ -0,0 +1,230 @@ +# 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. + +from marvin.cloudstackAPI import * +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.base import Account, User, Project, ProjectRole, ProjectRolePermission, PublicIPAddress +from marvin.lib.utils import cleanup_resources +from nose.plugins.attrib import attr +from random import shuffle + +import copy +import random +import re + + +class TestData(object): + """Test data object that is required to create resources + """ + + def __init__(self): + self.testdata = { + "account": { + "email": "user@test.com", + "firstname": "User", + "lastname": "User", + "username": "user", + "password": "password", + }, + "project": { + "name": "Test Project", + "displaytext": "Test project", + }, + "projectrole": { + "name": "MarvinFake project Role ", + "description": "Fake project Role created by Marvin test" + }, + "projectrolepermission": { + "rule": "listPublicIpAddresses", + "permission": "deny", + "description": "Fake role permission created by Marvin test" + } + } + + +class TestRoleBasedUsersInProjects(cloudstackTestCase): + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.testdata = TestData().testdata + + feature_enabled = self.apiclient.listCapabilities(listCapabilities.listCapabilitiesCmd()).dynamicrolesenabled + if not feature_enabled: + self.skipTest("Dynamic Role-Based API checker not enabled, skipping test") + + self.testdata["projectrole"]["name"] += self.getRandomString() + self.project = Project.create(self.apiclient, self.testdata["project"]) + self.projectrole = ProjectRole.create( + self.apiclient, + self.testdata["projectrole"], + self.project.id + ) + + self.testdata["projectrolepermission"]["projectroleid"] = self.projectrole.id + self.projectrolepermission = ProjectRolePermission.create( + self.apiclient, + self.testdata["projectrolepermission"], + self.project.id + ) + + self.cleanup = [] + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + self.debug("Warning! Exception in tearDown: %s" % e) + + def getUserApiClient(self, username, domain='ROOT', role_type='User'): + self.user_apiclient = self.testClient.getUserApiClient(UserName=username, DomainName=domain, + type=self.translateRoleToAccountType(role_type)) + return self.user_apiclient + + def getRandomString(self): + return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for _ in range(10)) + + def translateRoleToAccountType(self, role_type): + if role_type == "User": + return 0 + elif role_type == "Admin": + return 1 + elif role_type == "DomainAdmin": + return 2 + elif role_type == "ResourceAdmin": + return 3 + return -1 + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_add_account_to_project_with_project_role(self): + """ + 1. Create a User Account + 2. Add user account with 'Regular' project account role associate it with a Project role; + The role defines what APIs are allowed/disallowed for the user: here, 'listPublicIpAddresses' + is denied for the user account + 3. Execute the 'listPublicIpAddresses' API and verify/confirm that the API isn't allowed to be executed + by the account + """ + self.useraccount = Account.create( + self.apiclient, + self.testdata["account"], + roleid=4 + ) + self.cleanup.append(self.useraccount) + # Add account to the project + self.project.addAccount( + self.apiclient, + account=self.useraccount.name, + projectroleid=self.projectrole.id + ) + + self.userapiclient = self.testClient.getUserApiClient( + UserName=self.useraccount.name, + DomainName=self.useraccount.domain, + type=0) + try: + PublicIPAddress.list( + self.userapiclient, + projectid=self.project.id + ) + self.fail("API call succeeded which is denied for the project role") + except CloudstackAPIException: + pass + self.cleanup.append(self.project) + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_add_user_to_project_with_project_role(self): + """ + 1. Create a User Account + 2. Add user of an account with 'Regular' project account role associate it with a Project role; + The role defines what APIs are allowed/disallowed for the user: here, 'listPublicIpAddresses' + is denied for the user account + 3. Execute the 'listPublicIpAddresses' API and verify/confirm that the API isn't allowed to be executed + by the user + """ + self.useraccount = Account.create( + self.apiclient, + self.testdata["account"], + roleid=4 + ) + self.cleanup.append(self.useraccount) + # Add account to the project + self.project.addUser( + self.apiclient, + userid=self.useraccount.user[0].id, + projectroleid=self.projectrole.id + ) + Project.listAccounts(self.apiclient, projectid=self.project.id) + self.userapiclient = self.testClient.getUserApiClient( + UserName=self.useraccount.name, + DomainName=self.useraccount.domain, + type=0) + try: + PublicIPAddress.list( + self.userapiclient, + projectid=self.project.id + ) + self.fail("API call succeeded which is denied for the project role") + except CloudstackAPIException: + pass + self.cleanup.append(self.project) + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_add_multiple_admins_in_project(self): + """ + 1. Create a User Account + 2. Add user account with 'Admin' project account role and associate it with a Project role; + The role defines what APIs are allowed/disallowed for the user: here, 'listPublicIpAddresses' + is denied for the user account + 3. Execute the 'listPublicIpAddresses' API and verify/confirm that the user/account can execute the + API as it is a project admin + """ + self.useraccount = Account.create( + self.apiclient, + self.testdata["account"], + roleid=4 + ) + self.cleanup.append(self.useraccount) + # Add account to the project + self.project.addUser( + self.apiclient, + userid=self.useraccount.user[0].id, + roletype='Admin', + projectroleid=self.projectrole.id + ) + project_accounts = Project.listAccounts(self.apiclient, projectid=self.project.id) + admin_count = 0 + for acc in project_accounts: + if acc.role == 'Admin': + admin_count+=1 + + self.assertEqual(admin_count, 2, "account not added with admin Role") + + self.userapiclient = self.testClient.getUserApiClient( + UserName=self.useraccount.name, + DomainName=self.useraccount.domain, + type=0) + try: + PublicIPAddress.list( + self.userapiclient, + projectid=self.project.id + ) + self.debug("User added to the project could execute the listPublicIpAddresses API despite the project " + "role as it is the Admin") + except CloudstackAPIException: + pass + self.cleanup.append(self.project) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index df38bb54a2b4..b75950c054be 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -3966,7 +3966,7 @@ def __init__(self, items): self.__dict__.update(items) @classmethod - def create(cls, apiclient, services, account=None, domainid=None): + def create(cls, apiclient, services, account=None, domainid=None, userid=None, accountid=None): """Create project""" cmd = createProject.createProjectCmd() @@ -3976,7 +3976,10 @@ def create(cls, apiclient, services, account=None, domainid=None): cmd.account = account if domainid: cmd.domainid = domainid - + if userid: + cmd.userid = userid + if accountid: + cmd.accountid = accountid return Project(apiclient.createProject(cmd).__dict__) def delete(self, apiclient): @@ -4008,7 +4011,7 @@ def suspend(self, apiclient): cmd.id = self.id return apiclient.suspendProject(cmd) - def addAccount(self, apiclient, account=None, email=None): + def addAccount(self, apiclient, account=None, email=None, projectroleid=None, roletype=None): """Add account to project""" cmd = addAccountToProject.addAccountToProjectCmd() @@ -4017,6 +4020,10 @@ def addAccount(self, apiclient, account=None, email=None): cmd.account = account if email: cmd.email = email + if projectroleid: + cmd.projectroleid = projectroleid + if roletype: + cmd.roletype = roletype return apiclient.addAccountToProject(cmd) def deleteAccount(self, apiclient, account): @@ -4027,6 +4034,29 @@ def deleteAccount(self, apiclient, account): cmd.account = account return apiclient.deleteAccountFromProject(cmd) + def addUser(self, apiclient, userid=None, email=None, projectroleid=None, roletype=None): + """Add user to project""" + + cmd = addUserToProject.addUserToProjectCmd() + cmd.projectid = self.id + if userid: + cmd.userid = userid + if email: + cmd.email = email + if projectroleid: + cmd.projectroleid = projectroleid + if roletype: + cmd.roletype = roletype + return apiclient.addUserToProject(cmd) + + def deleteUser(self, apiclient, userid): + """Delete user from project""" + + cmd = deleteAccountFromProject.deleteAccountFromProjectCmd() + cmd.projectid = self.id + cmd.userid = userid + return apiclient.deleteUserFromProject(cmd) + @classmethod def listAccounts(cls, apiclient, **kwargs): """Lists all accounts associated with projects.""" @@ -4055,7 +4085,7 @@ def __init__(self, items): self.__dict__.update(items) @classmethod - def update(cls, apiclient, projectid, accept, account=None, token=None): + def update(cls, apiclient, projectid, accept, account=None, token=None, userid=None): """Updates the project invitation for that account""" cmd = updateProjectInvitation.updateProjectInvitationCmd() @@ -4063,6 +4093,8 @@ def update(cls, apiclient, projectid, accept, account=None, token=None): cmd.accept = accept if account: cmd.account = account + if userid: + cmd.userid = userid if token: cmd.token = token @@ -5390,3 +5422,90 @@ def restoreVM(self, apiclient): cmd = restoreBackup.restoreBackupCmd() cmd.id = self.id return (apiclient.restoreBackup(cmd)) + +class ProjectRole: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, projectid): + """Create project role""" + cmd = createProjectRole.createProjectRoleCmd() + cmd.projectid = projectid + cmd.name = services["name"] + if "description" in services: + cmd.description = services["description"] + + return ProjectRole(apiclient.createProjectRole(cmd).__dict__) + + def delete(self, apiclient, projectid): + """Delete project Role""" + + cmd = deleteProjectRole.deleteProjectRoleCmd() + cmd.projectid = projectid + cmd.id = self.id + apiclient.deleteProjectRole(cmd) + + def update(self, apiclient, projectid, **kwargs): + """Update the project role""" + + cmd = updateProjectRole.updateProjectRoleCmd() + cmd.projectid = projectid + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return apiclient.updateProjectRole(cmd) + + @classmethod + def list(cls, apiclient, projectid, **kwargs): + """List all project Roles matching criteria""" + + cmd = listProjectRoles.listProjectRolesCmd() + cmd.projectid = projectid + [setattr(cmd, k, v) for k, v in kwargs.items()] + return (apiclient.listProjectRoles(cmd)) + +class ProjectRolePermission: + """Manage Project Role Permission""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, projectid): + """Create role permission""" + cmd = createProjectRolePermission.createProjectRolePermissionCmd() + cmd.projectid = projectid + cmd.projectroleid = services["projectroleid"] + cmd.rule = services["rule"] + cmd.permission = services["permission"] + if "description" in services: + cmd.description = services["description"] + + return ProjectRolePermission(apiclient.createProjectRolePermission(cmd).__dict__) + + def delete(self, apiclient, projectid): + """Delete role permission""" + + cmd = deleteProjectRolePermission.deleteProjectRolePermissionCmd() + cmd.projectid = projectid + cmd.id = self.id + apiclient.deleteProjectRolePermission(cmd) + + def update(self, apiclient, projectid, **kwargs): + """Update the role permission""" + + cmd = updateProjectRolePermission.updateProjectRolePermissionCmd() + cmd.projectid = projectid + cmd.projectroleid = self.projectroleid + [setattr(cmd, k, v) for k, v in kwargs.items()] + return apiclient.updateProjectRolePermission(cmd) + + @classmethod + def list(cls, apiclient, projectid, **kwargs): + """List all role permissions matching criteria""" + + cmd = listProjectRolePermissions.listProjectRolePermissionsCmd() + cmd.projectid = projectid + [setattr(cmd, k, v) for k, v in kwargs.items()] + return (apiclient.listProjectRolePermissions(cmd)) diff --git a/utils/src/main/java/com/cloud/utils/HttpUtils.java b/utils/src/main/java/com/cloud/utils/HttpUtils.java index a5d9f6a16b67..f3adca71f928 100644 --- a/utils/src/main/java/com/cloud/utils/HttpUtils.java +++ b/utils/src/main/java/com/cloud/utils/HttpUtils.java @@ -19,13 +19,14 @@ package com.cloud.utils; -import org.apache.log4j.Logger; +import java.io.IOException; +import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.util.Map; + +import org.apache.log4j.Logger; public class HttpUtils { From 1f7a81bfc3319ed98cb291c56a0a2231ded129d8 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 19 Jun 2020 06:36:47 +0530 Subject: [PATCH 07/30] Refactored code --- .../java/com/cloud/api/dispatch/ParamProcessWorker.java | 3 +-- .../main/java/com/cloud/projects/ProjectManagerImpl.java | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java index 05814891d1bf..d4299861e71e 100644 --- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java @@ -31,7 +31,6 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; -import java.util.stream.Collectors; import javax.inject.Inject; @@ -275,7 +274,7 @@ private void doAccessChecks(BaseCmd cmd, Map entitiesToAcces List entityOwners = cmd.getEntityOwnerIds(); Account[] owners = null; if (entityOwners != null) { - owners = entityOwners.stream().map(id -> _accountMgr.getAccount(id)).collect(Collectors.toList()).toArray(new Account[0]); + owners = entityOwners.stream().map(id -> _accountMgr.getAccount(id)).toArray(Account[]::new); } else { owners = new Account[]{_accountMgr.getAccount(cmd.getEntityOwnerId())}; } diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 737f0858d32a..615d9350bd1c 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -685,7 +685,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Resour " doesn't belong to the project. Add it to the project first and then change the project's ownership"); } - if (isTheOnlyProjectOwnerOrOneself(projectId, newProjectAcc, caller) && newRole != Role.Admin) { + if (isTheOnlyProjectOwner(projectId, newProjectAcc, caller) && newRole != Role.Admin) { throw new InvalidParameterValueException("Cannot demote the only admin of the project"); } updateProjectAccount(newProjectAcc, newRole, updatedAcc.getId()); @@ -834,11 +834,10 @@ private boolean inviteUserToProject(Project project, User user, String email, Ro } } - private boolean isTheOnlyProjectOwnerOrOneself(Long projectId, ProjectAccount projectAccount, Account caller) { + private boolean isTheOnlyProjectOwner(Long projectId, ProjectAccount projectAccount, Account caller) { List projectOwners = _projectAccountDao.getProjectOwners(projectId); if ((projectOwners.size() == 1 && projectOwners.get(0).getAccountId() == projectAccount.getAccountId() && projectAccount.getAccountRole() == Role.Admin )) { - // || (projectAccount.getAccountId() == caller.getAccountId()) return true; } return false; @@ -886,7 +885,7 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { } //can't remove the owner of the project - if (isTheOnlyProjectOwnerOrOneself(projectId, projectAccount, caller)) { + if (isTheOnlyProjectOwner(projectId, projectAccount, caller)) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to delete account " + accountName + " from the project with specified id as the account is the owner of the project"); From 0fcb210ad601ad52cb8fb279d904d3fc1a77a715 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 22 Jun 2020 10:39:41 +0530 Subject: [PATCH 08/30] Add marvin tests --- ...est_enable_role_based_users_in_projects.py | 73 +++++++++++++++---- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/test/integration/smoke/test_enable_role_based_users_in_projects.py b/test/integration/smoke/test_enable_role_based_users_in_projects.py index 434ffa69575a..9a74fc0640bd 100644 --- a/test/integration/smoke/test_enable_role_based_users_in_projects.py +++ b/test/integration/smoke/test_enable_role_based_users_in_projects.py @@ -41,6 +41,13 @@ def __init__(self): "username": "user", "password": "password", }, + "useracc": { + "email": "user1@test.com", + "firstname": "User", + "lastname": "User", + "username": "user1", + "password": "fr3sca", + }, "project": { "name": "Test Project", "displaytext": "Test project", @@ -82,7 +89,7 @@ def setUp(self): self.project.id ) - self.cleanup = [] + self.cleanup = [self.project] def tearDown(self): try: @@ -144,7 +151,6 @@ def test_add_account_to_project_with_project_role(self): self.fail("API call succeeded which is denied for the project role") except CloudstackAPIException: pass - self.cleanup.append(self.project) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_add_user_to_project_with_project_role(self): @@ -181,7 +187,7 @@ def test_add_user_to_project_with_project_role(self): self.fail("API call succeeded which is denied for the project role") except CloudstackAPIException: pass - self.cleanup.append(self.project) + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_add_multiple_admins_in_project(self): @@ -199,32 +205,67 @@ def test_add_multiple_admins_in_project(self): roleid=4 ) self.cleanup.append(self.useraccount) - # Add account to the project - self.project.addUser( + + self.useraccount1 = Account.create( self.apiclient, - userid=self.useraccount.user[0].id, - roletype='Admin', + self.testdata["useracc"], + roleid=4 + ) + + self.cleanup.append(self.useraccount1) + + self.project.addAccount( + self.apiclient, + account=self.useraccount.name, + projectroleid=self.projectrole.id, + roletype='Admin' + ) + + self.project.addAccount( + self.apiclient, + account=self.useraccount1.name, projectroleid=self.projectrole.id ) - project_accounts = Project.listAccounts(self.apiclient, projectid=self.project.id) - admin_count = 0 - for acc in project_accounts: - if acc.role == 'Admin': - admin_count+=1 + project_accounts = Project.listAccounts(self.apiclient, projectid=self.project.id, role='Admin') - self.assertEqual(admin_count, 2, "account not added with admin Role") + self.assertEqual(len(project_accounts), 2, "account not added with admin Role") - self.userapiclient = self.testClient.getUserApiClient( + self.userapiclientAdminRole = self.testClient.getUserApiClient( UserName=self.useraccount.name, DomainName=self.useraccount.domain, type=0) + + self.userapiclientRegularRole = self.testClient.getUserApiClient( + UserName=self.useraccount1.name, + DomainName=self.useraccount1.domain, + type=0) + try: PublicIPAddress.list( - self.userapiclient, + self.userapiclientAdminRole, projectid=self.project.id ) self.debug("User added to the project could execute the listPublicIpAddresses API despite the project " "role as it is the Admin") + pass except CloudstackAPIException: + self.fail("User is an Admin, should be able to execute the command despite Project role") + + try: + self.project.suspend( + self.userapiclientAdminRole, + ) + self.debug("The user can perform Project administrative operations as it is added as " + "an Admin to the project") + pass + except CloudstackAPIException: + self.fail("User should be allowed to execute project administrative operations" + "as it is the Project Admin") + + try: + self.project.suspend( + self.userapiclientRegularRole, + ) + except Exception as e: pass - self.cleanup.append(self.project) + From f37e9689396a641b5ee04d2fcd6a875cc77ad6da Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 24 Jun 2020 17:50:27 +0530 Subject: [PATCH 09/30] Permit root admin to perform operation on/in projects they aren't part of --- .../cloudstack/acl/ProjectRoleBasedApiAccessChecker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index a01f809d73f6..1bfab78d21ad 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -74,6 +74,10 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe return true; } + if (accountService.isRootAdmin(userAccount.getId())) { + return true; + } + ProjectAccount projectUser = projectAccountDao.findByProjectIdUserId(project.getId(), userAccount.getAccountId(), user.getId()); if (projectUser != null) { if (projectUser.getAccountRole() == ProjectAccount.Role.Admin) { From 96fc8776b8d506409fe9d33ceed08216c7cef307 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 25 Jun 2020 16:30:15 +0530 Subject: [PATCH 10/30] Permit domain admins --- .../apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 1bfab78d21ad..24e493f930ef 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -74,7 +74,7 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe return true; } - if (accountService.isRootAdmin(userAccount.getId())) { + if (accountService.isRootAdmin(userAccount.getId()) || accountService.isDomainAdmin(userAccount.getAccountId())) { return true; } From ab3b5a12a7a4f7e94114b369b16e4c692f149680 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 29 Jun 2020 11:03:26 +0530 Subject: [PATCH 11/30] fix query parameter for listing project role permissions --- .../cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java | 4 ++-- .../org/apache/cloudstack/acl/ProjectRoleManagerImpl.java | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java index 547c916b41e9..e7db8f54fff2 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java @@ -51,7 +51,7 @@ public ProjectRolePermissionsDaoImpl() { ProjectRolePermissionsSearch = createSearchBuilder(); ProjectRolePermissionsSearch.and("uuid", ProjectRolePermissionsSearch.entity().getUuid(), SearchCriteria.Op.EQ); - ProjectRolePermissionsSearch.and("projectRoleId", ProjectRolePermissionsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + ProjectRolePermissionsSearch.and("projectRoleId", ProjectRolePermissionsSearch.entity().getProjectRoleId(), SearchCriteria.Op.EQ); ProjectRolePermissionsSearch.and("projectId", ProjectRolePermissionsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); ProjectRolePermissionsSearch.and("sortOrder", ProjectRolePermissionsSearch.entity().getSortOrder(), SearchCriteria.Op.EQ); ProjectRolePermissionsSearch.done(); @@ -65,7 +65,7 @@ public ProjectRolePermissionsDaoImpl() { public List findAllByRoleIdSorted(Long roleId, Long projectId) { final SearchCriteria sc = ProjectRolePermissionsSearch.create(); if (roleId != null && roleId > 0L) { - sc.setParameters("roleId", roleId); + sc.setParameters("projectRoleId", roleId); } if (projectId != null && projectId > 0L) { sc.setParameters("projectId", projectId); diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index b56494f9c0a5..0f8f84336f4a 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -182,7 +182,11 @@ public ProjectRolePermission createProjectRolePermission(CreateProjectRolePermis return Transaction.execute(new TransactionCallback() { @Override public ProjectRolePermissionVO doInTransaction(TransactionStatus status) { - return projRolePermissionsDao.persist(new ProjectRolePermissionVO(projectId, projectRoleId, rule.toString(), permission, description)); + try { + return projRolePermissionsDao.persist(new ProjectRolePermissionVO(projectId, projectRoleId, rule.toString(), permission, description)); + } catch (Exception e) { + throw new CloudRuntimeException("Project role permission for " + rule.toString()+ " seems to already exist."); + } } }); } From c1bf01884d12b6ba9a94fe817013707325c7b8c2 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 29 Jun 2020 17:06:18 +0530 Subject: [PATCH 12/30] review comments (offline) --- api/src/main/java/com/cloud/event/EventTypes.java | 1 - .../org/apache/cloudstack/acl/ProjectRoleManagerImpl.java | 2 +- .../java/org/apache/cloudstack/acl/RoleManagerImpl.java | 2 -- .../test/java/com/cloud/user/AccountManagerImplTest.java | 7 ------- utils/src/main/java/com/cloud/utils/HttpUtils.java | 7 +++---- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 2ccbfc9e2173..443dd568df91 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -193,7 +193,6 @@ public class EventTypes { public static final String EVENT_PROJECT_ROLE_UPDATE = "PROJECT.ROLE.UPDATE"; public static final String EVENT_PROJECT_ROLE_DELETE = "PROJECT.ROLE.DELETE"; public static final String EVENT_PROJECT_ROLE_PERMISSION_CREATE = "PROJECT.ROLE.PERMISSION.CREATE"; - public static final String EVENT_PROJECT_ROLE_PERMISSION_UPDATE_ORDER = "PROJECT.ROLE.PERMISSION.UPDATE"; public static final String EVENT_PROJECT_ROLE_PERMISSION_UPDATE = "PROJECT.ROLE.PERMISSION.UPDATE"; public static final String EVENT_PROJECT_ROLE_PERMISSION_DELETE = "PROJECT.ROLE.PERMISSION.DELETE"; diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index 0f8f84336f4a..0bd30d771efd 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -192,7 +192,7 @@ public ProjectRolePermissionVO doInTransaction(TransactionStatus status) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_UPDATE_ORDER, eventDescription = "updating Project Role Permission order") + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ROLE_PERMISSION_UPDATE, eventDescription = "updating Project Role Permission order") public boolean updateProjectRolePermission(Long projectId, ProjectRole projectRole, List rolePermissionsOrder) { checkAccess(projectId); return projectRole != null && rolePermissionsOrder != null && projRolePermissionsDao.update(projectRole, projectId, rolePermissionsOrder); diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java index b44c729f4d43..b4e4cd788a76 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java @@ -35,7 +35,6 @@ import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRolePermissionCmd; -import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRoleCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -327,7 +326,6 @@ public List> getCommands() { cmdList.add(ListRolePermissionsCmd.class); cmdList.add(UpdateRolePermissionCmd.class); cmdList.add(DeleteRolePermissionCmd.class); - cmdList.add(CreateProjectRoleCmd.class); return cmdList; } } diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 8494d18748ca..e37507b98e48 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -368,13 +368,6 @@ public void retrieveAndValidateAccountTestAccountTypeEqualsSystemType() { Mockito.doReturn(Account.ACCOUNT_ID_SYSTEM).when(userVoMock).getAccountId(); Mockito.doReturn(Account.ACCOUNT_ID_SYSTEM).when(accountMock).getId(); Mockito.doReturn(callingAccount).when(_accountDao).findById(Account.ACCOUNT_ID_SYSTEM); - // TODO - remove: -// CallContext.current().setProject(project); -// Mockito.lenient().doReturn(1L).when(project).getId(); -// Mockito.lenient().doReturn(1L).when(_user).getId(); -// Mockito.lenient().doReturn(1L).when(accountMock).getAccountId(); -// Mockito.lenient().doReturn(projectAccountVO).when(_projectAccountDao).findByProjectIdUserId(anyLong(), anyLong(), anyLong()); -// Mockito.lenient().doReturn(ProjectAccount.Role.Admin).when(projectAccountVO).getProjectRole(); accountManagerImpl.retrieveAndValidateAccount(userVoMock); } diff --git a/utils/src/main/java/com/cloud/utils/HttpUtils.java b/utils/src/main/java/com/cloud/utils/HttpUtils.java index f3adca71f928..a5d9f6a16b67 100644 --- a/utils/src/main/java/com/cloud/utils/HttpUtils.java +++ b/utils/src/main/java/com/cloud/utils/HttpUtils.java @@ -19,14 +19,13 @@ package com.cloud.utils; -import java.io.IOException; -import java.util.Map; +import org.apache.log4j.Logger; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; - -import org.apache.log4j.Logger; +import java.io.IOException; +import java.util.Map; public class HttpUtils { From d719c81efb732296eab4c641c2c33866efd47bb7 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 11 May 2020 15:49:26 +0530 Subject: [PATCH 13/30] Dynamic roles improvements. Add-on functionality below. - Create a role from any of the existing role, using new parameter roleid in createRole API - Import a role with its rules, using a new importRole API - New default roles for Read-Only and Support Admin & User - No modifications allowed for Default roles - Cleaned up old NetApp APIs from role_permissions table. --- .../main/java/com/cloud/event/EventTypes.java | 2 + .../java/org/apache/cloudstack/acl/Role.java | 1 + .../apache/cloudstack/acl/RoleService.java | 9 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../cloudstack/api/ApiServerService.java | 1 + .../api/command/admin/acl/CreateRoleCmd.java | 50 ++- .../api/command/admin/acl/ImportRoleCmd.java | 149 +++++++++ .../api/command/admin/acl/ListRolesCmd.java | 1 + .../api/command/admin/acl/RoleCmd.java | 30 ++ .../api/command/admin/acl/UpdateRoleCmd.java | 15 - .../cloudstack/api/response/RoleResponse.java | 8 + .../api/command/test/CreateRoleCmdTest.java | 102 +++++++ .../api/command/test/ImportRoleCmdTest.java | 131 ++++++++ .../upgrade/dao/Upgrade41400to41500.java | 286 ++++++++++++++++++ .../org/apache/cloudstack/acl/RoleVO.java | 11 + .../apache/cloudstack/acl/dao/RoleDao.java | 3 + .../cloudstack/acl/dao/RoleDaoImpl.java | 22 ++ .../acl/dao/RolePermissionsDao.java | 8 + .../acl/dao/RolePermissionsDaoImpl.java | 14 + .../db/schema-41400to41500-cleanup.sql | 3 + .../META-INF/db/schema-41400to41500.sql | 9 + .../main/java/com/cloud/api/ApiServer.java | 11 + .../cloudstack/acl/RoleManagerImpl.java | 137 ++++++++- test/integration/smoke/test_dynamicroles.py | 160 ++++++++-- tools/marvin/marvin/lib/base.py | 19 +- 25 files changed, 1119 insertions(+), 64 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java create mode 100644 api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java create mode 100644 api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index ec8089078c9a..f8d0a38e543a 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -184,6 +184,7 @@ public class EventTypes { public static final String EVENT_ROLE_CREATE = "ROLE.CREATE"; public static final String EVENT_ROLE_UPDATE = "ROLE.UPDATE"; public static final String EVENT_ROLE_DELETE = "ROLE.DELETE"; + public static final String EVENT_ROLE_IMPORT = "ROLE.IMPORT"; public static final String EVENT_ROLE_PERMISSION_CREATE = "ROLE.PERMISSION.CREATE"; public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE"; public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE"; @@ -689,6 +690,7 @@ public class EventTypes { entityEventDetails.put(EVENT_ROLE_CREATE, Role.class); entityEventDetails.put(EVENT_ROLE_UPDATE, Role.class); entityEventDetails.put(EVENT_ROLE_DELETE, Role.class); + entityEventDetails.put(EVENT_ROLE_IMPORT, Role.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_CREATE, RolePermission.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_UPDATE, RolePermission.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_DELETE, RolePermission.class); diff --git a/api/src/main/java/org/apache/cloudstack/acl/Role.java b/api/src/main/java/org/apache/cloudstack/acl/Role.java index b05d886fdbfc..a31ec524c526 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/Role.java +++ b/api/src/main/java/org/apache/cloudstack/acl/Role.java @@ -24,4 +24,5 @@ public interface Role extends InternalIdentity, Identity { String getName(); RoleType getRoleType(); String getDescription(); + boolean isDefault(); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java index 6130c62a7d45..d7eef923ff6a 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.acl; import java.util.List; +import java.util.Map; import org.apache.cloudstack.acl.RolePermission.Permission; import org.apache.cloudstack.framework.config.ConfigKey; @@ -39,13 +40,17 @@ public interface RoleService { Role createRole(String name, RoleType roleType, String description); + Role createRole(String name, Role role, String description); + + Role importRole(String name, RoleType roleType, String description, List> rules, boolean forced); + Role updateRole(Role role, String name, RoleType roleType, String description); boolean deleteRole(Role role); RolePermission findRolePermission(Long id); - RolePermission findRolePermissionByUuid(String uuid); + RolePermission findRolePermissionByRoleIdAndRule(Long roleId, String rule); RolePermission createRolePermission(Role role, Rule rule, Permission permission, String description); @@ -77,4 +82,6 @@ public interface RoleService { List findRolesByType(RoleType roleType); List findAllPermissionsBy(Long roleId); + + Permission getRolePermission(String permission); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 8510554cec2d..073c94049106 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -478,6 +478,7 @@ public class ApiConstants { public static final String ROLE_NAME = "rolename"; public static final String PERMISSION = "permission"; public static final String RULE = "rule"; + public static final String RULES = "rules"; public static final String RULE_ID = "ruleid"; public static final String RULE_ORDER = "ruleorder"; public static final String USER = "user"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index 382b48a5e026..d00112b91dbe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -43,4 +43,5 @@ public ResponseObject loginUser(HttpSession session, String username, String pas public Class getCmdClass(String cmdName); + public boolean isValidApiName(String apiName); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java index 87f0288dd01d..82cdcbd1d413 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java @@ -45,13 +45,9 @@ public class CreateRoleCmd extends RoleCmd { description = "creates a role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty}) private String roleName; - @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, - description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User", - validations = {ApiArgValidator.NotNullOrEmpty}) - private String roleType; - - @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role") - private String roleDescription; + @Parameter(name = ApiConstants.ROLE_ID, type = CommandType.UUID, entityType = RoleResponse.class, + description = "ID of the role to be cloned from. Either roleid or type must be passed in") + private Long roleId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -61,12 +57,8 @@ public String getRoleName() { return roleName; } - public RoleType getRoleType() { - return RoleType.fromString(roleType); - } - - public String getRoleDescription() { - return roleDescription; + public Long getRoleId() { + return roleId; } ///////////////////////////////////////////////////// @@ -85,11 +77,39 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Role: " + getRoleName() + ", type:" + getRoleType() + ", description: " + getRoleDescription()); - final Role role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription()); + validateRoleParameters(); + + Role role = null; + if (getRoleId() != null) { + Role existingRole = roleService.findRole(getRoleId()); + if (existingRole == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided"); + } + + CallContext.current().setEventDetails("Role: " + getRoleName() + ", from role: " + getRoleId() + ", description: " + getRoleDescription()); + role = roleService.createRole(getRoleName(), existingRole, getRoleDescription()); + } else { + CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription()); + role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription()); + } + if (role == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create role"); } setupResponse(role); } + + private void validateRoleParameters() { + if (getRoleType() == null && getRoleId() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Neither role type nor role ID is provided"); + } + + if (getRoleType() != null && getRoleId() != null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Both role type and role ID should not be specified"); + } + + if (getRoleId() != null && getRoleId() < 1L) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided"); + } + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java new file mode 100644 index 000000000000..3afac9fbea02 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java @@ -0,0 +1,149 @@ +// 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.api.command.admin.acl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.Rule; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ApiServerService; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.RoleResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.collections.MapUtils; + +import com.cloud.user.Account; +import com.google.common.base.Strings; + +@APICommand(name = ImportRoleCmd.APINAME, description = "Imports a role based on provided map of rule permissions", responseObject = RoleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.15.0", + authorized = {RoleType.Admin}) +public class ImportRoleCmd extends RoleCmd { + public static final String APINAME = "importRole"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "Creates a role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty}) + private String roleName; + + @Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true, + description = "Rules param list, rule and permission is must. Example: rules[0].rule=create*&rules[0].permission=allow&rules[0].description=create%20rule&rules[1].rule=list*&rules[1].permission=allow&rules[1].description=listing") + private Map rules; + + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, + description = "Force create a role with the same name. This overrides the role type, description and rule permissions for the existing role. Default is false.") + private Boolean forced; + + @Inject + ApiServerService _apiServer; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getRoleName() { + return roleName; + } + + // Returns list of rule maps. Each map corresponds to a rule with the details in the keys: rule, permission & description + public List> getRules() { + if (MapUtils.isEmpty(rules)) { + return null; + } + + List> rulesDetails = new ArrayList<>(); + Collection rulesCollection = rules.values(); + Iterator iter = rulesCollection.iterator(); + while (iter.hasNext()) { + HashMap detail = (HashMap)iter.next(); + Map ruleDetails = new HashMap<>(); + String rule = detail.get(ApiConstants.RULE); + if (Strings.isNullOrEmpty(rule)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Empty rule provided in rules param"); + } + if (!rule.contains("*") && !_apiServer.isValidApiName(rule)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid api name: " + rule + " provided in rules param"); + } + ruleDetails.put(ApiConstants.RULE, new Rule(rule)); + + String permission = detail.get(ApiConstants.PERMISSION); + if (Strings.isNullOrEmpty(permission)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid permission: "+ permission + " provided in rules param"); + } + ruleDetails.put(ApiConstants.PERMISSION, roleService.getRolePermission(permission)); + + String description = detail.get(ApiConstants.DESCRIPTION); + if (!Strings.isNullOrEmpty(permission)) { + ruleDetails.put(ApiConstants.DESCRIPTION, description); + } + + rulesDetails.add(ruleDetails); + } + return rulesDetails; + } + + public boolean isForced() { + return (forced != null) ? forced : false; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + if (getRoleType() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided"); + } + + CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription()); + Role role = roleService.importRole(getRoleName(), getRoleType(), getRoleDescription(), getRules(), isForced()); + if (role == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to import role"); + } + setupResponse(role); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java index 9025e89a93cd..24b8a3e831ae 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java @@ -97,6 +97,7 @@ private void setupResponse(final List roles) { roleResponse.setRoleName(role.getName()); roleResponse.setRoleType(role.getRoleType()); roleResponse.setDescription(role.getDescription()); + roleResponse.setIsDefault(role.isDefault()); roleResponse.setObjectName("role"); roleResponses.add(roleResponse); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java index 9054ff5fadaf..440278ba9117 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java @@ -18,11 +18,41 @@ package org.apache.cloudstack.api.command.admin.acl; import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.RoleResponse; +import com.google.common.base.Strings; + public abstract class RoleCmd extends BaseCmd { + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User") + private String roleType; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role") + private String roleDescription; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public RoleType getRoleType() { + if (!Strings.isNullOrEmpty(roleType)) { + return RoleType.fromString(roleType); + } + return null; + } + + public String getRoleDescription() { + return roleDescription; + } + protected void setupResponse(final Role role) { final RoleResponse response = new RoleResponse(); response.setId(role.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java index f9519aeec5a1..0137d1e317cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java @@ -18,7 +18,6 @@ package org.apache.cloudstack.api.command.admin.acl; import com.cloud.user.Account; -import com.google.common.base.Strings; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; @@ -49,9 +48,6 @@ public class UpdateRoleCmd extends RoleCmd { @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, description = "creates a role with this unique name") private String roleName; - @Parameter(name = ApiConstants.TYPE, type = BaseCmd.CommandType.STRING, description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User") - private String roleType; - @Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "The description of the role") private String roleDescription; @@ -67,17 +63,6 @@ public String getRoleName() { return roleName; } - public RoleType getRoleType() { - if (!Strings.isNullOrEmpty(roleType)) { - return RoleType.fromString(roleType); - } - return null; - } - - public String getRoleDescription() { - return roleDescription; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java index fd4bf28e6184..88154fa3e164 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java @@ -43,6 +43,10 @@ public class RoleResponse extends BaseResponse { @Param(description = "the description of the role") private String roleDescription; + @SerializedName(ApiConstants.IS_DEFAULT) + @Param(description = "true if role is default, false otherwise") + private Boolean isDefault; + public void setId(String id) { this.id = id; } @@ -60,4 +64,8 @@ public void setRoleType(RoleType roleType) { public void setDescription(String description) { this.roleDescription = description; } + + public void setIsDefault(Boolean isDefault) { + this.isDefault = isDefault; + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java new file mode 100644 index 000000000000..a910de789a50 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java @@ -0,0 +1,102 @@ +// 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.api.command.test; + +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd; +import org.apache.cloudstack.api.response.RoleResponse; +import org.apache.cloudstack.api.ServerApiException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.when; + +public class CreateRoleCmdTest { + private CreateRoleCmd createRoleCmd; + private RoleService roleService; + private Role role; + + @Before + public void setUp() { + roleService = Mockito.spy(RoleService.class); + createRoleCmd = new CreateRoleCmd(); + ReflectionTestUtils.setField(createRoleCmd,"roleService",roleService); + ReflectionTestUtils.setField(createRoleCmd,"roleName","testuser"); + ReflectionTestUtils.setField(createRoleCmd,"roleDescription","User test"); + role = Mockito.mock(Role.class); + } + + @Test + public void testCreateRoleWithRoleType() { + ReflectionTestUtils.setField(createRoleCmd,"roleType", "User"); + when(role.getId()).thenReturn(1L); + when(role.getUuid()).thenReturn("12345-abcgdkajd"); + when(role.getDescription()).thenReturn("User test"); + when(role.getName()).thenReturn("testuser"); + when(role.getRoleType()).thenReturn(RoleType.User); + when(roleService.createRole(createRoleCmd.getRoleName(), createRoleCmd.getRoleType(), createRoleCmd.getRoleDescription())).thenReturn(role); + createRoleCmd.execute(); + RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject(); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), role.getName()); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), role.getDescription()); + } + + @Test + public void testCreateRoleWithExistingRole() { + ReflectionTestUtils.setField(createRoleCmd,"roleId",1L); + when(roleService.findRole(createRoleCmd.getRoleId())).thenReturn(role); + Role newRole = Mockito.mock(Role.class); + when(newRole.getId()).thenReturn(2L); + when(newRole.getUuid()).thenReturn("67890-xyztestid"); + when(newRole.getDescription()).thenReturn("User test"); + when(newRole.getName()).thenReturn("testuser"); + when(newRole.getRoleType()).thenReturn(RoleType.User); + when(roleService.createRole(createRoleCmd.getRoleName(), role, createRoleCmd.getRoleDescription())).thenReturn(newRole); + createRoleCmd.execute(); + RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject(); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), newRole.getName()); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), newRole.getDescription()); + } + + @Test(expected = ServerApiException.class) + public void testCreateRoleWithNonExistingRole() { + ReflectionTestUtils.setField(createRoleCmd,"roleId",1L); + when(roleService.findRole(createRoleCmd.getRoleId())).thenReturn(null); + createRoleCmd.execute(); + Assert.fail("An exception should have been thrown: " + ServerApiException.class); + } + + @Test(expected = ServerApiException.class) + public void testCreateRoleValidateNeitherRoleIdNorTypeParameters() { + createRoleCmd.execute(); + Assert.fail("An exception should have been thrown: " + ServerApiException.class); + } + + @Test(expected = ServerApiException.class) + public void testCreateRoleValidateBothRoleIdAndTypeParameters() { + ReflectionTestUtils.setField(createRoleCmd,"roleId",1L); + ReflectionTestUtils.setField(createRoleCmd,"roleType", "User"); + createRoleCmd.execute(); + Assert.fail("An exception should have been thrown: " + ServerApiException.class); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java new file mode 100644 index 000000000000..8de01489dab1 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java @@ -0,0 +1,131 @@ +// 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.api.command.test; + +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.admin.acl.ImportRoleCmd; +import org.apache.cloudstack.api.response.RoleResponse; +import org.apache.cloudstack.api.ServerApiException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.cloud.exception.InvalidParameterValueException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +public class ImportRoleCmdTest { + private ImportRoleCmd importRoleCmd; + private RoleService roleService; + private Role role; + + @Before + public void setUp() { + roleService = Mockito.spy(RoleService.class); + importRoleCmd = new ImportRoleCmd(); + ReflectionTestUtils.setField(importRoleCmd,"roleService",roleService); + ReflectionTestUtils.setField(importRoleCmd,"roleName","Test User"); + ReflectionTestUtils.setField(importRoleCmd,"roleType", "User"); + ReflectionTestUtils.setField(importRoleCmd,"roleDescription","test user imported"); + role = Mockito.mock(Role.class); + } + + @Test + public void testImportRoleSuccess() { + Map> rules = new HashMap>(); + + //Rule 1 + Map rule1 = new HashMap(); + rule1.put(ApiConstants.RULE, "list*"); + rule1.put(ApiConstants.PERMISSION, "allow"); + rule1.put(ApiConstants.DESCRIPTION, "listing apis"); + rules.put("key1", rule1); + + //Rule 2 + Map rule2 = new HashMap(); + rule2.put(ApiConstants.RULE, "update*"); + rule2.put(ApiConstants.PERMISSION, "deny"); + rule2.put(ApiConstants.DESCRIPTION, "no update allowed"); + rules.put("key2", rule2); + + //Rule 3 + Map rule3 = new HashMap(); + rule3.put(ApiConstants.RULE, "get*"); + rule3.put(ApiConstants.PERMISSION, "allow"); + rule3.put(ApiConstants.DESCRIPTION, "get details"); + rules.put("key3", rule3); + + ReflectionTestUtils.setField(importRoleCmd,"rules",rules); + + when(role.getUuid()).thenReturn("12345-abcgdkajd"); + when(role.getDescription()).thenReturn("test user imported"); + when(role.getName()).thenReturn("Test User"); + when(role.getRoleType()).thenReturn(RoleType.User); + when(roleService.importRole(anyString(),any(), anyString(), any(), anyBoolean())).thenReturn(role); + + importRoleCmd.execute(); + RoleResponse response = (RoleResponse) importRoleCmd.getResponseObject(); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), role.getName()); + Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), role.getDescription()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testImportRoleInvalidRule() { + Map> rules = new HashMap>(); + Map rule = new HashMap(); + rule.put(ApiConstants.RULE, "*?+test*"); + rule.put(ApiConstants.PERMISSION, "allow"); + rule.put(ApiConstants.DESCRIPTION, "listing apis"); + rules.put("key1", rule); + ReflectionTestUtils.setField(importRoleCmd,"rules",rules); + + importRoleCmd.execute(); + Assert.fail("An exception should have been thrown: " + InvalidParameterValueException.class); + } + + @Test(expected = ServerApiException.class) + public void testImportRoleInvalidPermission() { + Map> rules = new HashMap>(); + Map rule = new HashMap(); + rule.put(ApiConstants.RULE, "list*"); + rule.put(ApiConstants.PERMISSION, "pass"); + rule.put(ApiConstants.DESCRIPTION, "listing apis"); + rules.put("key1", rule); + ReflectionTestUtils.setField(importRoleCmd,"rules",rules); + + importRoleCmd.execute(); + Assert.fail("An exception should have been thrown: " + ServerApiException.class); + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java index 13c67d9d6fa0..e3fe6022331c 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41400to41500.java @@ -22,8 +22,11 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -65,6 +68,7 @@ public InputStream[] getPrepareScripts() { @Override public void performDataMigration(Connection conn) { updateSystemVmTemplates(conn); + addRolePermissionsForNewReadOnlyAndSupportRoles(conn); } @SuppressWarnings("serial") @@ -235,6 +239,288 @@ private void updateSystemVmTemplates(final Connection conn) { LOG.debug("Updating System Vm Template IDs Complete"); } + private void addRolePermissionsForNewReadOnlyAndSupportRoles(final Connection conn) { + addRolePermissionsForReadOnlyAdmin(conn); + addRolePermissionsForReadOnlyUser(conn); + addRolePermissionsForSupportAdmin(conn); + addRolePermissionsForSupportUser(conn); + } + + private void addRolePermissionsForReadOnlyAdmin(final Connection conn) { + LOG.debug("Adding role permissions for new read-only admin role"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only Admin - Default' AND is_default = 1"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long readOnlyAdminRoleId = rs.getLong(1); + int readOnlyAdminSortOrder = 0; + Map readOnlyAdminRules = new LinkedHashMap<>(); + readOnlyAdminRules.put("list*", "ALLOW"); + readOnlyAdminRules.put("getUploadParamsFor*", "DENY"); + readOnlyAdminRules.put("get*", "ALLOW"); + readOnlyAdminRules.put("cloudianIsEnabled", "ALLOW"); + readOnlyAdminRules.put("queryAsyncJobResult", "ALLOW"); + readOnlyAdminRules.put("quotaIsEnabled", "ALLOW"); + readOnlyAdminRules.put("quotaTariffList", "ALLOW"); + readOnlyAdminRules.put("quotaSummary", "ALLOW"); + readOnlyAdminRules.put("*", "DENY"); + + for (Map.Entry readOnlyAdminRule : readOnlyAdminRules.entrySet()) { + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, ?, ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, readOnlyAdminRoleId); + pstmt.setString(2, readOnlyAdminRule.getKey()); + pstmt.setString(3, readOnlyAdminRule.getValue()); + pstmt.setLong(4, readOnlyAdminSortOrder++); + pstmt.executeUpdate(); + } + } + + if (rs != null && !rs.isClosed()) { + rs.close(); + } + if (pstmt != null && !pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully added role permissions for new read-only admin role"); + } catch (final SQLException e) { + LOG.error("Exception while adding role permissions for read-only admin role: " + e.getMessage()); + throw new CloudRuntimeException("Exception while adding role permissions for read-only admin role: " + e.getMessage(), e); + } + } + + private void addRolePermissionsForReadOnlyUser(final Connection conn) { + LOG.debug("Adding role permissions for new read-only user role"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only User - Default' AND is_default = 1"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long readOnlyUserRoleId = rs.getLong(1); + int readOnlyUserSortOrder = 0; + + pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = 4 AND permission = 'ALLOW' AND rule LIKE 'list%' ORDER BY sort_order"); + ResultSet rsRolePermissions = pstmt.executeQuery(); + + while (rsRolePermissions.next()) { + String rule = rsRolePermissions.getString(1); + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, readOnlyUserRoleId); + pstmt.setString(2, rule); + pstmt.setLong(3, readOnlyUserSortOrder++); + pstmt.executeUpdate(); + } + + pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = 4 AND permission = 'ALLOW' AND rule LIKE 'get%' AND rule NOT LIKE 'getUploadParamsFor%' ORDER BY sort_order"); + rsRolePermissions = pstmt.executeQuery(); + + while (rsRolePermissions.next()) { + String rule = rsRolePermissions.getString(1); + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, readOnlyUserRoleId); + pstmt.setString(2, rule); + pstmt.setLong(3, readOnlyUserSortOrder++); + pstmt.executeUpdate(); + } + + List readOnlyUserRulesAllowed = new ArrayList<>(); + readOnlyUserRulesAllowed.add("cloudianIsEnabled"); + readOnlyUserRulesAllowed.add("queryAsyncJobResult"); + readOnlyUserRulesAllowed.add("quotaIsEnabled"); + readOnlyUserRulesAllowed.add("quotaTariffList"); + readOnlyUserRulesAllowed.add("quotaSummary"); + + for(String readOnlyUserRule : readOnlyUserRulesAllowed) { + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, readOnlyUserRoleId); + pstmt.setString(2, readOnlyUserRule); + pstmt.setLong(3, readOnlyUserSortOrder++); + pstmt.executeUpdate(); + } + + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, readOnlyUserRoleId); + pstmt.setLong(2, readOnlyUserSortOrder); + pstmt.executeUpdate(); + + if (rsRolePermissions != null && !rsRolePermissions.isClosed()) { + rsRolePermissions.close(); + } + } + + if (rs != null && !rs.isClosed()) { + rs.close(); + } + if (pstmt != null && !pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully added role permissions for new read-only user role"); + } catch (final SQLException e) { + LOG.error("Exception while adding role permissions for read-only user role: " + e.getMessage()); + throw new CloudRuntimeException("Exception while adding role permissions for read-only user role: " + e.getMessage(), e); + } + } + + private void addRolePermissionsForSupportAdmin(final Connection conn) { + LOG.debug("Adding role permissions for new support admin role"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Support Admin - Default' AND is_default = 1"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long supportAdminRoleId = rs.getLong(1); + int supportAdminSortOrder = 0; + + pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only Admin - Default' AND is_default = 1"); + ResultSet rsReadOnlyAdmin = pstmt.executeQuery(); + if (rsReadOnlyAdmin.next()) { + long readOnlyAdminRoleId = rsReadOnlyAdmin.getLong(1); + pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = ? AND permission = 'ALLOW' ORDER BY sort_order"); + pstmt.setLong(1, readOnlyAdminRoleId); + ResultSet rsRolePermissions = pstmt.executeQuery(); + + while (rsRolePermissions.next()) { + String rule = rsRolePermissions.getString(1); + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportAdminRoleId); + pstmt.setString(2, rule); + pstmt.setLong(3, supportAdminSortOrder++); + pstmt.executeUpdate(); + } + + List supportAdminRulesAllowed = new ArrayList<>(); + supportAdminRulesAllowed.add("prepareHostForMaintenance"); + supportAdminRulesAllowed.add("cancelHostMaintenance"); + supportAdminRulesAllowed.add("enableStorageMaintenance"); + supportAdminRulesAllowed.add("cancelStorageMaintenance"); + supportAdminRulesAllowed.add("createServiceOffering"); + supportAdminRulesAllowed.add("createDiskOffering"); + supportAdminRulesAllowed.add("createNetworkOffering"); + supportAdminRulesAllowed.add("createVPCOffering"); + supportAdminRulesAllowed.add("startVirtualMachine"); + supportAdminRulesAllowed.add("stopVirtualMachine"); + supportAdminRulesAllowed.add("rebootVirtualMachine"); + supportAdminRulesAllowed.add("startKubernetesCluster"); + supportAdminRulesAllowed.add("stopKubernetesCluster"); + supportAdminRulesAllowed.add("createVolume"); + supportAdminRulesAllowed.add("attachVolume"); + supportAdminRulesAllowed.add("detachVolume"); + supportAdminRulesAllowed.add("uploadVolume"); + supportAdminRulesAllowed.add("attachIso"); + supportAdminRulesAllowed.add("detachIso"); + supportAdminRulesAllowed.add("registerTemplate"); + supportAdminRulesAllowed.add("registerIso"); + + for(String supportAdminRule : supportAdminRulesAllowed) { + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportAdminRoleId); + pstmt.setString(2, supportAdminRule); + pstmt.setLong(3, supportAdminSortOrder++); + pstmt.executeUpdate(); + } + + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportAdminRoleId); + pstmt.setLong(2, supportAdminSortOrder); + pstmt.executeUpdate(); + + if (rsRolePermissions != null && !rsRolePermissions.isClosed()) { + rsRolePermissions.close(); + } + } + + if (rsReadOnlyAdmin != null && !rsReadOnlyAdmin.isClosed()) { + rsReadOnlyAdmin.close(); + } + } + + if (rs != null && !rs.isClosed()) { + rs.close(); + } + if (pstmt != null && !pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully added role permissions for new support admin role"); + } catch (final SQLException e) { + LOG.error("Exception while adding role permissions for support admin role: " + e.getMessage()); + throw new CloudRuntimeException("Exception while adding role permissions for support admin role: " + e.getMessage(), e); + } + } + + private void addRolePermissionsForSupportUser(final Connection conn) { + LOG.debug("Adding role permissions for new support user role"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Support User - Default' AND is_default = 1"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long supportUserRoleId = rs.getLong(1); + int supportUserSortOrder = 0; + + pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only User - Default' AND is_default = 1"); + ResultSet rsReadOnlyUser = pstmt.executeQuery(); + if (rsReadOnlyUser.next()) { + long readOnlyUserRoleId = rsReadOnlyUser.getLong(1); + pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = ? AND permission = 'ALLOW' ORDER BY sort_order"); + pstmt.setLong(1, readOnlyUserRoleId); + ResultSet rsRolePermissions = pstmt.executeQuery(); + while (rsRolePermissions.next()) { + String rule = rsRolePermissions.getString(1); + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportUserRoleId); + pstmt.setString(2, rule); + pstmt.setLong(3, supportUserSortOrder++); + pstmt.executeUpdate(); + } + + List supportUserRulesAllowed = new ArrayList<>(); + supportUserRulesAllowed.add("startVirtualMachine"); + supportUserRulesAllowed.add("stopVirtualMachine"); + supportUserRulesAllowed.add("rebootVirtualMachine"); + supportUserRulesAllowed.add("startKubernetesCluster"); + supportUserRulesAllowed.add("stopKubernetesCluster"); + supportUserRulesAllowed.add("createVolume"); + supportUserRulesAllowed.add("attachVolume"); + supportUserRulesAllowed.add("detachVolume"); + supportUserRulesAllowed.add("uploadVolume"); + supportUserRulesAllowed.add("attachIso"); + supportUserRulesAllowed.add("detachIso"); + supportUserRulesAllowed.add("registerTemplate"); + supportUserRulesAllowed.add("registerIso"); + supportUserRulesAllowed.add("getUploadParamsFor*"); + + for(String supportUserRule : supportUserRulesAllowed) { + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportUserRoleId); + pstmt.setString(2, supportUserRule); + pstmt.setLong(3, supportUserSortOrder++); + pstmt.executeUpdate(); + } + + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule"); + pstmt.setLong(1, supportUserRoleId); + pstmt.setLong(2, supportUserSortOrder); + pstmt.executeUpdate(); + + if (rsRolePermissions != null && !rsRolePermissions.isClosed()) { + rsRolePermissions.close(); + } + } + + if (rsReadOnlyUser != null && !rsReadOnlyUser.isClosed()) { + rsReadOnlyUser.close(); + } + } + + if (rs != null && !rs.isClosed()) { + rs.close(); + } + if (pstmt != null && !pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully added role permissions for new support user role"); + } catch (final SQLException e) { + LOG.error("Exception while adding role permissions for support user role: " + e.getMessage()); + throw new CloudRuntimeException("Exception while adding role permissions for support user role: " + e.getMessage(), e); + } + } + @Override public InputStream[] getCleanupScripts() { final String scriptFile = "META-INF/db/schema-41400to41500-cleanup.sql"; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java index f3404ab6d791..823dc69e8701 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java @@ -51,6 +51,9 @@ public class RoleVO implements Role { @Column(name = "description") private String description; + @Column(name = "is_default") + private boolean isDefault = false; + @Column(name = GenericDao.REMOVED_COLUMN) private Date removed; @@ -75,6 +78,10 @@ public String getUuid() { return uuid; } + public void setUuid(String uuid) { + this.uuid = uuid; + } + @Override public long getId() { return id; @@ -103,4 +110,8 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + public boolean isDefault() { + return isDefault; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java index e53654d998e6..4a53965a5a26 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.acl.dao; import com.cloud.utils.db.GenericDao; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.RoleVO; @@ -26,4 +27,6 @@ public interface RoleDao extends GenericDao { List findAllByName(String roleName); List findAllByRoleType(RoleType type); + List findByName(String roleName); + RoleVO findByNameAndType(String roleName, RoleType type); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java index e3dd266d1315..4dc11fc88e6e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java @@ -20,6 +20,7 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.RoleVO; import org.springframework.stereotype.Component; @@ -30,6 +31,7 @@ public class RoleDaoImpl extends GenericDaoBase implements RoleDao { private final SearchBuilder RoleByNameSearch; private final SearchBuilder RoleByTypeSearch; + private final SearchBuilder RoleByNameAndTypeSearch; public RoleDaoImpl() { super(); @@ -41,6 +43,11 @@ public RoleDaoImpl() { RoleByTypeSearch = createSearchBuilder(); RoleByTypeSearch.and("roleType", RoleByTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ); RoleByTypeSearch.done(); + + RoleByNameAndTypeSearch = createSearchBuilder(); + RoleByNameAndTypeSearch.and("roleName", RoleByNameAndTypeSearch.entity().getName(), SearchCriteria.Op.EQ); + RoleByNameAndTypeSearch.and("roleType", RoleByNameAndTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ); + RoleByNameAndTypeSearch.done(); } @Override @@ -56,4 +63,19 @@ public List findAllByRoleType(final RoleType type) { sc.setParameters("roleType", type); return listBy(sc); } + + @Override + public List findByName(String roleName) { + SearchCriteria sc = RoleByNameSearch.create(); + sc.setParameters("roleName", roleName); + return listBy(sc); + } + + @Override + public RoleVO findByNameAndType(String roleName, RoleType type) { + SearchCriteria sc = RoleByNameAndTypeSearch.create(); + sc.setParameters("roleName", roleName); + sc.setParameters("roleType", type); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java index c9aeba1c5998..dec12512311f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java @@ -56,4 +56,12 @@ public interface RolePermissionsDao extends GenericDao { * @return returns list of role permissions */ List findAllByRoleIdSorted(Long roleId); + + /** + * Returns role permission for a given role and rule + * @param roleId the ID of the role + * @param roleId rule for the role + * @return returns role permission + */ + RolePermissionVO findByRoleIdAndRule(Long roleId, String rule); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java index 68b6abf21953..5b4f7a7582f1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java @@ -44,12 +44,18 @@ public class RolePermissionsDaoImpl extends GenericDaoBase implements RolePermissionsDao { protected static final Logger LOGGER = Logger.getLogger(RolePermissionsDaoImpl.class); + private final SearchBuilder RolePermissionsSearchByRoleAndRule; private final SearchBuilder RolePermissionsSearch; private Attribute sortOrderAttribute; public RolePermissionsDaoImpl() { super(); + RolePermissionsSearchByRoleAndRule = createSearchBuilder(); + RolePermissionsSearchByRoleAndRule.and("roleId", RolePermissionsSearchByRoleAndRule.entity().getRoleId(), SearchCriteria.Op.EQ); + RolePermissionsSearchByRoleAndRule.and("rule", RolePermissionsSearchByRoleAndRule.entity().getRule(), SearchCriteria.Op.EQ); + RolePermissionsSearchByRoleAndRule.done(); + RolePermissionsSearch = createSearchBuilder(); RolePermissionsSearch.and("uuid", RolePermissionsSearch.entity().getUuid(), SearchCriteria.Op.EQ); RolePermissionsSearch.and("roleId", RolePermissionsSearch.entity().getRoleId(), SearchCriteria.Op.EQ); @@ -174,4 +180,12 @@ public List findAllByRoleIdSorted(final Long roleId) { } return rolePermissionList; } + + @Override + public RolePermissionVO findByRoleIdAndRule(final Long roleId, final String rule) { + final SearchCriteria sc = RolePermissionsSearchByRoleAndRule.create(); + sc.setParameters("roleId", roleId); + sc.setParameters("rule", rule); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql index fe04728cb9a2..e9df02a275c1 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500-cleanup.sql @@ -18,3 +18,6 @@ --; -- Schema upgrade cleanup from 4.14.0.0 to 4.15.0.0 --; + +-- remove the old NetApp storage APIs (unsupported since 4.12) from role_permissions +DELETE from `cloud`.`role_permissions` WHERE rule IN ('createPool', 'modifyPool', 'deletePool', 'listPools', 'associateLun', 'dissociateLun', 'createLunOnFiler', 'destroyLunOnFiler', 'listLunsOnFiler', 'createVolumeOnFiler', 'destroyVolumeOnFiler', 'listVolumesOnFiler'); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index 5ec50cd0f1b9..c8d280a350f9 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -18,3 +18,12 @@ --; -- Schema upgrade from 4.14.0.0 to 4.15.0.0 --; + +ALTER TABLE `cloud`.`roles` ADD COLUMN `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'is this a default role'; +UPDATE `cloud`.`roles` SET `is_default` = 1 WHERE id IN (1, 2, 3, 4); + +-- Updated Default CloudStack roles with read-only and support admin and user roles +INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Read-Only Admin - Default', 'Admin', 'Default read-only admin role', 1); +INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Read-Only User - Default', 'User', 'Default read-only user role', 1); +INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Support Admin - Default', 'Admin', 'Default support admin role', 1); +INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Support User - Default', 'User', 'Default support user role', 1); diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index 7d096b869a8f..a0a71d099b76 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -1204,6 +1204,17 @@ else if (cmdList.size() == 1) } } + @Override + public boolean isValidApiName(String apiName) { + if (apiName == null || apiName.isEmpty()) + return false; + + if (!s_apiNameCmdClassMap.containsKey(apiName)) + return false; + + return true; + } + // FIXME: rather than isError, we might was to pass in the status code to give more flexibility private void writeResponse(final HttpResponse resp, final String responseText, final int statusCode, final String responseType, final String reasonPhrase) { try { diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java index ae471b2486ca..a51680c217a0 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java @@ -20,17 +20,20 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.inject.Inject; import org.apache.cloudstack.acl.dao.RoleDao; import org.apache.cloudstack.acl.dao.RolePermissionsDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd; import org.apache.cloudstack.api.command.admin.acl.CreateRolePermissionCmd; import org.apache.cloudstack.api.command.admin.acl.DeleteRoleCmd; import org.apache.cloudstack.api.command.admin.acl.DeleteRolePermissionCmd; +import org.apache.cloudstack.api.command.admin.acl.ImportRoleCmd; import org.apache.cloudstack.api.command.admin.acl.ListRolePermissionsCmd; import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd; @@ -54,6 +57,7 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; import com.google.common.base.Strings; public class RoleManagerImpl extends ManagerBase implements RoleService, Configurable, PluggableService { @@ -124,11 +128,12 @@ public RolePermission findRolePermission(final Long id) { } @Override - public RolePermission findRolePermissionByUuid(final String uuid) { - if (Strings.isNullOrEmpty(uuid)) { + public RolePermission findRolePermissionByRoleIdAndRule(final Long roleId, final String rule) { + if (roleId == null || Strings.isNullOrEmpty(rule)) { return null; } - return rolePermissionsDao.findByUuid(uuid); + + return rolePermissionsDao.findByRoleIdAndRule(roleId, rule); } @Override @@ -146,10 +151,99 @@ public RoleVO doInTransaction(TransactionStatus status) { }); } + @Override + @ActionEvent(eventType = EventTypes.EVENT_ROLE_CREATE, eventDescription = "creating role by cloning another role") + public Role createRole(String name, Role role, String description) { + checkCallerAccess(); + return Transaction.execute(new TransactionCallback() { + @Override + public RoleVO doInTransaction(TransactionStatus status) { + RoleVO newRoleVO = roleDao.persist(new RoleVO(name, role.getRoleType(), description)); + if (newRoleVO == null) { + throw new CloudRuntimeException("Unable to create the role: " + name + ", failed to persist in DB"); + } + + List rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(role.getId()); + if (rolePermissions != null && !rolePermissions.isEmpty()) { + for (RolePermissionVO permission : rolePermissions) { + rolePermissionsDao.persist(new RolePermissionVO(newRoleVO.getId(), permission.getRule().toString(), permission.getPermission(), permission.getDescription())); + } + } + + return newRoleVO; + } + }); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ROLE_IMPORT, eventDescription = "importing Role") + public Role importRole(String name, RoleType type, String description, List> rules, boolean forced) { + checkCallerAccess(); + if (Strings.isNullOrEmpty(name)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role name provided"); + } + if (type == null || type == RoleType.Unknown) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided"); + } + + List existingRoles = roleDao.findByName(name); + if (CollectionUtils.isNotEmpty(existingRoles) && !forced) { + throw new CloudRuntimeException("Role already exists"); + } + + return Transaction.execute(new TransactionCallback() { + @Override + public RoleVO doInTransaction(TransactionStatus status) { + RoleVO newRole = null; + RoleVO existingRole = roleDao.findByNameAndType(name, type); + if (existingRole != null) { + if (existingRole.isDefault()) { + throw new CloudRuntimeException("Failed to import the role: " + name + ", default role cannot be overriden"); + } + + //Cleanup old role permissions + List rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(existingRole.getId()); + if (rolePermissions != null && !rolePermissions.isEmpty()) { + for (RolePermission rolePermission : rolePermissions) { + rolePermissionsDao.remove(rolePermission.getId()); + } + } + + existingRole.setName(name); + existingRole.setRoleType(type); + existingRole.setDescription(description); + roleDao.update(existingRole.getId(), existingRole); + + newRole = existingRole; + } else { + newRole = roleDao.persist(new RoleVO(name, type, description)); + } + + if (newRole == null) { + throw new CloudRuntimeException("Unable to import the role: " + name + ", failed to persist in DB"); + } + + if (rules != null && !rules.isEmpty()) { + for (Map ruleDetail : rules) { + Rule rule = (Rule)ruleDetail.get(ApiConstants.RULE); + RolePermission.Permission rulePermission = (RolePermission.Permission) ruleDetail.get(ApiConstants.PERMISSION); + String ruleDescription = (String) ruleDetail.get(ApiConstants.DESCRIPTION); + + rolePermissionsDao.persist(new RolePermissionVO(newRole.getId(), rule.toString(), rulePermission, ruleDescription)); + } + } + return newRole; + } + }); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ROLE_UPDATE, eventDescription = "updating Role") public Role updateRole(final Role role, final String name, final RoleType roleType, final String description) { checkCallerAccess(); + if (role.isDefault()) { + throw new PermissionDeniedException("Default roles cannot be updated"); + } if (roleType != null && roleType == RoleType.Unknown) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unknown is not a valid role type"); @@ -159,9 +253,6 @@ public Role updateRole(final Role role, final String name, final RoleType roleTy roleVO.setName(name); } if (roleType != null) { - if (role.getId() <= RoleType.User.getId()) { - throw new PermissionDeniedException("The role type of default roles cannot be changed"); - } List accounts = accountDao.findAccountsByRole(role.getId()); if (accounts == null || accounts.isEmpty()) { roleVO.setRoleType(roleType); @@ -184,7 +275,7 @@ public boolean deleteRole(final Role role) { if (role == null) { return false; } - if (role.getId() <= RoleType.User.getId()) { + if (role.isDefault()) { throw new PermissionDeniedException("Default roles cannot be deleted"); } List accounts = accountDao.findAccountsByRole(role.getId()); @@ -214,6 +305,14 @@ public Boolean doInTransaction(TransactionStatus status) { @ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_CREATE, eventDescription = "creating Role Permission") public RolePermission createRolePermission(final Role role, final Rule rule, final RolePermission.Permission permission, final String description) { checkCallerAccess(); + if (role.isDefault()) { + throw new PermissionDeniedException("Role permission cannot be added for Default roles"); + } + + if (findRolePermissionByRoleIdAndRule(role.getId(), rule.toString()) != null) { + throw new PermissionDeniedException("Rule already exists for the role: " + role.getName()); + } + return Transaction.execute(new TransactionCallback() { @Override public RolePermissionVO doInTransaction(TransactionStatus status) { @@ -226,12 +325,18 @@ public RolePermissionVO doInTransaction(TransactionStatus status) { @ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_UPDATE, eventDescription = "updating Role Permission order") public boolean updateRolePermission(final Role role, final List newOrder) { checkCallerAccess(); + if (role.isDefault()) { + throw new PermissionDeniedException("Role permission cannot be updated for Default roles"); + } return role != null && newOrder != null && rolePermissionsDao.update(role, newOrder); } @Override public boolean updateRolePermission(Role role, RolePermission rolePermission, RolePermission.Permission permission) { checkCallerAccess(); + if (role.isDefault()) { + throw new PermissionDeniedException("Role permission cannot be updated for Default roles"); + } return role != null && rolePermissionsDao.update(role, rolePermission, permission); } @@ -239,6 +344,10 @@ public boolean updateRolePermission(Role role, RolePermission rolePermission, Ro @ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_DELETE, eventDescription = "deleting Role Permission") public boolean deleteRolePermission(final RolePermission rolePermission) { checkCallerAccess(); + Role role = findRole(rolePermission.getRoleId()); + if (role.isDefault()) { + throw new PermissionDeniedException("Role permission cannot be deleted for Default roles"); + } return rolePermission != null && rolePermissionsDao.remove(rolePermission.getId()); } @@ -277,7 +386,6 @@ protected void removeRootAdminRoles(List roles) { rolesIterator.remove(); } } - } @Override @@ -305,6 +413,18 @@ public List findAllPermissionsBy(final Long roleId) { return Collections.emptyList(); } + @Override + public RolePermission.Permission getRolePermission(String permission) { + if (Strings.isNullOrEmpty(permission)) { + return null; + } + if (!permission.equalsIgnoreCase(RolePermission.Permission.ALLOW.toString()) && + !permission.equalsIgnoreCase(RolePermission.Permission.DENY.toString())) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Values for permission parameter should be: allow or deny"); + } + return permission.equalsIgnoreCase(RolePermission.Permission.ALLOW.toString()) ? RolePermission.Permission.ALLOW : RolePermission.Permission.DENY; + } + @Override public String getConfigComponentName() { return RoleService.class.getSimpleName(); @@ -319,6 +439,7 @@ public ConfigKey[] getConfigKeys() { public List> getCommands() { final List> cmdList = new ArrayList<>(); cmdList.add(CreateRoleCmd.class); + cmdList.add(ImportRoleCmd.class); cmdList.add(ListRolesCmd.class); cmdList.add(UpdateRoleCmd.class); cmdList.add(DeleteRoleCmd.class); diff --git a/test/integration/smoke/test_dynamicroles.py b/test/integration/smoke/test_dynamicroles.py index 61e1d199741a..2ce70011c2c6 100644 --- a/test/integration/smoke/test_dynamicroles.py +++ b/test/integration/smoke/test_dynamicroles.py @@ -45,6 +45,14 @@ def __init__(self): "type": "User", "description": "Fake Role created by Marvin test" }, + "importrole": { + "name": "MarvinFake Import Role ", + "type": "User", + "description": "Fake Import User Role created by Marvin test", + "rules" : [{"rule":"list*", "permission":"allow","description":"Listing apis"}, + {"rule":"get*", "permission":"allow","description":"Get apis"}, + {"rule":"update*", "permission":"deny","description":"Update apis"}] + }, "roleadmin": { "name": "MarvinFake Admin Role ", "type": "Admin", @@ -201,6 +209,91 @@ def test_role_lifecycle_create(self): msg="Role type does not match the test data" ) + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_role_lifecycle_clone(self): + """ + Tests create role from existing role + """ + # Use self.role created in setUp() + role_to_be_cloned = { + "name": "MarvinFake Clone Role ", + "roleid": self.role.id, + "description": "Fake Role cloned by Marvin test" + } + + try: + role_cloned = Role.create( + self.apiclient, + role_to_be_cloned + ) + self.cleanup.append(role_cloned) + except CloudstackAPIException as e: + self.fail("Failed to create the role: %s" % e) + + list_role_cloned= Role.list(self.apiclient, id=role_cloned.id) + self.assertEqual( + isinstance(list_role_cloned, list), + True, + "List Roles response was not a valid list" + ) + self.assertEqual( + len(list_role_cloned), + 1, + "List Roles response size was not 1" + ) + self.assertEqual( + list_role_cloned[0].name, + role_to_be_cloned["name"], + msg="Role name does not match the test data" + ) + self.assertEqual( + list_role_cloned[0].type, + self.testdata["role"]["type"], + msg="Role type does not match the test data" + ) + + list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id) + self.validate_permissions_list(list_rolepermissions, role_cloned.id) + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_role_lifecycle_import(self): + """ + Tests import role with the rules + """ + # use importrole from testdata + self.testdata["importrole"]["name"] += self.getRandomString() + try: + role_imported = Role.importRole( + self.apiclient, + self.testdata["importrole"] + ) + self.cleanup.append(role_imported) + except CloudstackAPIException as e: + self.fail("Failed to import the role: %s" % e) + + list_role_imported = Role.list(self.apiclient, id=role_imported.id) + self.assertEqual( + isinstance(list_role_imported, list), + True, + "List Roles response was not a valid list" + ) + self.assertEqual( + len(list_role_imported), + 1, + "List Roles response size was not 1" + ) + self.assertEqual( + list_role_imported[0].name, + self.testdata["importrole"]["name"], + msg="Role name does not match the test data" + ) + self.assertEqual( + list_role_imported[0].type, + self.testdata["importrole"]["type"], + msg="Role type does not match the test data" + ) + + self.validate_permissions_dict(self.testdata["importrole"]["rules"], role_imported.id) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_role_lifecycle_update(self): @@ -360,44 +453,23 @@ def test_rolepermission_lifecycle_update(self): self.cleanup.append(permission) permissions.append(permission) - - def validate_permissions_list(permissions): - list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id) - self.assertEqual( - len(list_rolepermissions), - len(permissions), - msg="List of role permissions do not match created list of permissions" - ) - - for idx, rolepermission in enumerate(list_rolepermissions): - self.assertEqual( - rolepermission.rule, - permissions[idx].rule, - msg="Rule permission don't match with expected item at the index" - ) - self.assertEqual( - rolepermission.permission, - permissions[idx].permission, - msg="Rule permission don't match with expected item at the index" - ) - # Move last item to the top rule = permissions.pop(len(permissions)-1) permissions = [rule] + permissions rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions))) - validate_permissions_list(permissions) + self.validate_permissions_list(permissions, self.role.id) # Move to the bottom rule = permissions.pop(0) permissions = permissions + [rule] rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions))) - validate_permissions_list(permissions) + self.validate_permissions_list(permissions, self.role.id) # Random shuffles for _ in range(3): shuffle(permissions) rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions))) - validate_permissions_list(permissions) + self.validate_permissions_list(permissions, self.role.id) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_rolepermission_lifecycle_update_permission(self): @@ -580,3 +652,43 @@ def test_role_account_acls_multiple_mgmt_servers(self): # Perform actual API call for allow API self.checkApiCall(apiConfig, userApiClient) + + def validate_permissions_list(self, permissions, roleid): + list_rolepermissions = RolePermission.list(self.apiclient, roleid=roleid) + self.assertEqual( + len(list_rolepermissions), + len(permissions), + msg="List of role permissions do not match created list of permissions" + ) + + for idx, rolepermission in enumerate(list_rolepermissions): + self.assertEqual( + rolepermission.rule, + permissions[idx].rule, + msg="Rule permission don't match with expected item at the index" + ) + self.assertEqual( + rolepermission.permission, + permissions[idx].permission, + msg="Rule permission don't match with expected item at the index" + ) + + def validate_permissions_dict(self, permissions, roleid): + list_rolepermissions = RolePermission.list(self.apiclient, roleid=roleid) + self.assertEqual( + len(list_rolepermissions), + len(permissions), + msg="List of role permissions do not match created list of permissions" + ) + + for idx, rolepermission in enumerate(list_rolepermissions): + self.assertEqual( + rolepermission.rule, + permissions[idx]["rule"], + msg="Rule permission don't match with expected item at the index" + ) + self.assertEqual( + rolepermission.permission, + permissions[idx]["permission"], + msg="Rule permission don't match with expected item at the index" + ) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index df38bb54a2b4..df37757f4564 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -102,12 +102,29 @@ def create(cls, apiclient, services, domainid=None): """Create role""" cmd = createRole.createRoleCmd() cmd.name = services["name"] - cmd.type = services["type"] + if "type" in services: + cmd.type = services["type"] + if "roleid" in services: + cmd.roleid = services["roleid"] if "description" in services: cmd.description = services["description"] return Role(apiclient.createRole(cmd).__dict__) + @classmethod + def importRole(cls, apiclient, services, domainid=None): + """Import role""" + cmd = importRole.importRoleCmd() + cmd.name = services["name"] + cmd.type = services["type"] + cmd.rules = services["rules"] + if "description" in services: + cmd.description = services["description"] + if "forced" in services: + cmd.type = services["forced"] + + return Role(apiclient.importRole(cmd).__dict__) + def delete(self, apiclient): """Delete Role""" From 3e73a91b35876756421087ca45db9a5109f4e847 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 3 Jul 2020 10:18:34 +0530 Subject: [PATCH 14/30] Review comments addressed --- .../apache/cloudstack/api/ApiConstants.java | 4 +- .../acl/project/CreateProjectRoleCmd.java | 3 +- .../CreateProjectRolePermissionCmd.java | 5 ++- .../acl/project/DeleteProjectRoleCmd.java | 3 +- .../DeleteProjectRolePermissionCmd.java | 5 ++- .../ListProjectRolePermissionsCmd.java | 5 ++- .../acl/project/ListProjectRolesCmd.java | 4 +- .../acl/project/UpdateProjectRoleCmd.java | 3 +- .../UpdateProjectRolePermissionCmd.java | 11 ++--- .../user/account/AddUserToProjectCmd.java | 3 +- .../account/DeleteUserFromProjectCmd.java | 3 +- .../acl/dao/ProjectRoleDaoImpl.java | 5 ++- .../META-INF/db/schema-41400to41500.sql | 40 ------------------- 13 files changed, 32 insertions(+), 62 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index a7b42bad7c5b..616ea26b1575 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -473,16 +473,14 @@ public class ApiConstants { public static final String PROJECT = "project"; public static final String ROLE = "role"; public static final String ROLE_ID = "roleid"; - public static final String ACCOUNT_ROLE_TYPE = "accountroletype"; public static final String PROJECT_ROLE_ID = "projectroleid"; public static final String PROJECT_ROLE_NAME = "projectrolename"; - public static final String PROJECT_ROLE_TYPE = "projectroletype"; public static final String ROLE_TYPE = "roletype"; public static final String ROLE_NAME = "rolename"; public static final String PERMISSION = "permission"; public static final String RULE = "rule"; public static final String RULE_ID = "ruleid"; - public static final String PROJECT_RULE_ID = "projectruleid"; + public static final String PROJECT_ROLE_PERMISSION_ID = "projectrolepermissionid"; public static final String RULE_ORDER = "ruleorder"; public static final String USER = "user"; public static final String ACTIVE_ONLY = "activeonly"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java index d0a02e9c8e5e..a3fa14955c15 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.acl.project; import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -32,7 +33,7 @@ @APICommand(name = CreateProjectRoleCmd.APINAME, description = "Creates a Project role", responseObject = ProjectRoleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}, since = "4.15.0") public class CreateProjectRoleCmd extends ProjectRoleCmd { public static final String APINAME = "createProjectRole"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java index e6e11da195fb..c39cc1d1bf4d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java @@ -19,6 +19,7 @@ import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -32,8 +33,8 @@ import org.apache.cloudstack.context.CallContext; @APICommand(name = CreateProjectRolePermissionCmd.APINAME, description = "Adds API permissions to a project role", responseObject = ProjectRolePermissionResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.15.0") public class CreateProjectRolePermissionCmd extends BaseRolePermissionCmd { public static final String APINAME = "createProjectRolePermission"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java index ec5140f83de4..c1a4d3912511 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java @@ -31,10 +31,9 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; - @APICommand(name = DeleteProjectRoleCmd.APINAME, description = "Delete Project roles in CloudStack", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.15.0", authorized = { - RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin}) + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class DeleteProjectRoleCmd extends BaseCmd { public static final String APINAME = "deleteProjectRole" ; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java index 38cb6ab1442c..17e74824102f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.acl.project; import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -31,8 +32,8 @@ import org.apache.cloudstack.context.CallContext; @APICommand(name = DeleteProjectRolePermissionCmd.APINAME, description = "Deletes a project role permission in the project", responseObject = SuccessResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.15.0") public class DeleteProjectRolePermissionCmd extends BaseCmd { public static final String APINAME = "deleteProjectRolePermission"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java index f64019b8fad7..ca3d1a075c1c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -35,8 +36,8 @@ import org.apache.cloudstack.context.CallContext; @APICommand(name = ListProjectRolePermissionsCmd.APINAME, description = "Lists a project's project role permissions", responseObject = SuccessResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.15.0") public class ListProjectRolePermissionsCmd extends BaseCmd { public static final String APINAME = "listProjectRolePermissions"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java index 79a9f86a0b0c..0a5886aac024 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; @@ -33,7 +34,8 @@ import org.apache.commons.lang3.StringUtils; @APICommand(name = ListProjectRolesCmd.APINAME, description = "Lists Project roles in CloudStack", responseObject = ProjectRoleResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.15.0") + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.15.0", authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class ListProjectRolesCmd extends BaseCmd { public static final String APINAME = "listProjectRoles"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java index 5577d3d317b7..41f68645e9b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.acl.project; import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -29,7 +30,7 @@ @APICommand(name = UpdateProjectRoleCmd.APINAME, description = "Creates a Project role", responseObject = ProjectRoleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}, since = "4.15.0") public class UpdateProjectRoleCmd extends ProjectRoleCmd { public static final String APINAME = "updateProjectRole"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java index 2891cb270330..a0a83acf3eb3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -38,8 +39,8 @@ import org.apache.commons.lang3.EnumUtils; @APICommand(name = UpdateProjectRolePermissionCmd.APINAME, description = "Updates a project role permission and/or order", responseObject = SuccessResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, - since = "4.15.0") + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.15.0") public class UpdateProjectRolePermissionCmd extends BaseCmd { public static final String APINAME = "updateProjectRolePermission"; @@ -59,12 +60,12 @@ public class UpdateProjectRolePermissionCmd extends BaseCmd { description = "The parent role permission uuid, use 0 to move this rule at the top of the list") private List projectRulePermissionOrder; - @Parameter(name = ApiConstants.PROJECT_RULE_ID, type = CommandType.UUID, entityType = ProjectRolePermissionResponse.class, - description = "Project Role permission rule id", since="4.11") + @Parameter(name = ApiConstants.PROJECT_ROLE_PERMISSION_ID, type = CommandType.UUID, entityType = ProjectRolePermissionResponse.class, + description = "Project Role permission rule id") private Long projectRuleId; @Parameter(name = ApiConstants.PERMISSION, type = CommandType.STRING, - description = "Rule permission, can be: allow or deny", since="4.11") + description = "Rule permission, can be: allow or deny") private String projectRolePermission; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index ec58336b2fc0..1587a5555271 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.account; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -37,7 +38,7 @@ import com.cloud.projects.ProjectAccount; @APICommand(name = AddUserToProjectCmd.APINAME, description = "Adds user to a project", responseObject = SuccessResponse.class, since = "4.14", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) public class AddUserToProjectCmd extends BaseAsyncCmd { public static final String APINAME = "addUserToProject"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java index 06195b6a3e94..38cb5a2011c7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java @@ -19,6 +19,7 @@ import java.util.List; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -38,7 +39,7 @@ import com.cloud.projects.Project; @APICommand(name = DeleteUserFromProjectCmd.APINAME, description = "Deletes user from the project", responseObject = SuccessResponse.class, since = "4.15.0", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) public class DeleteUserFromProjectCmd extends BaseAsyncCmd { public static final Logger LOGGER = Logger.getLogger(DeleteProjectCmd.class.getName()); public static final String APINAME = "deleteUserFromProject"; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java index 9322503e1a45..b15382612f48 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java @@ -24,6 +24,7 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.google.common.base.Strings; public class ProjectRoleDaoImpl extends GenericDaoBase implements ProjectRoleDao{ private final SearchBuilder ProjectRoleSearch; @@ -40,7 +41,9 @@ public ProjectRoleDaoImpl() { @Override public List findByName(String name, Long projectId) { SearchCriteria sc = ProjectRoleSearch.create(); - sc.setParameters("name", "%" + name + "%"); + if (!Strings.isNullOrEmpty(name)) { + sc.setParameters("name", "%" + name + "%"); + } if (projectId != null) { sc.setParameters("project_id", projectId); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index b5bb3579a531..cf42aef7cca9 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -18,46 +18,6 @@ --; -- Schema upgrade from 4.14.0.0 to 4.15.0.0 --; --- [ADD/DELETE USER TO PROJECT] add role permission for adding user to project - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'addUserToProject', 'ALLOW', 2) ON DUPLICATE KEY UPDATE rule=rule; - - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteUserFromProject', 'ALLOW', 71) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteUserFromProject', 'ALLOW', 69) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteUserFromProject', 'ALLOW', 55) ON DUPLICATE KEY UPDATE rule=rule; - --- [PROJECT ROLE] add role permissions for the CRUD API commands for projectRole - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'createProjectRole', 'ALLOW', 51) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteProjectRole', 'ALLOW', 98) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'listProjectRoles', 'ALLOW', 201) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'updateProjectRole', 'ALLOW', 301) ON DUPLICATE KEY UPDATE rule=rule; - - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'createProjectRole', 'ALLOW', 49) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteProjectRole', 'ALLOW', 93) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'listProjectRoles', 'ALLOW', 188) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'updateProjectRole', 'ALLOW', 285) ON DUPLICATE KEY UPDATE rule=rule; - - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'createProjectRole', 'ALLOW', 39) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteProjectRole', 'ALLOW', 78) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'listProjectRoles', 'ALLOW', 161) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'updateProjectRole', 'ALLOW', 246) ON DUPLICATE KEY UPDATE rule=rule; - --- [PROJECT ROLE PERMISSION] add role permissions for the CRUD API commands for projectRolePermission - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'createProjectRolePermission', 'ALLOW', 52) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'deleteProjectRolePermission', 'ALLOW', 99) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'listProjectRolePermissions', 'ALLOW', 202) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'updateProjectRolePermission', 'ALLOW', 302) ON DUPLICATE KEY UPDATE rule=rule; - - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'createProjectRolePermission', 'ALLOW', 50) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'deleteProjectRolePermission', 'ALLOW', 94) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'listProjectRolePermissions', 'ALLOW', 189) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'updateProjectRolePermission', 'ALLOW', 286) ON DUPLICATE KEY UPDATE rule=rule; - - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'createProjectRolePermission', 'ALLOW', 40) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'deleteProjectRolePermission', 'ALLOW', 79) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'listProjectRolePermissions', 'ALLOW', 162) ON DUPLICATE KEY UPDATE rule=rule; - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'updateProjectRolePermission', 'ALLOW', 247) ON DUPLICATE KEY UPDATE rule=rule; -- Project roles CREATE TABLE IF NOT EXISTS `cloud`.`project_role` ( From 497d72d5966d0fb1c8efaf35080fa1a415fddefe Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 3 Jul 2020 15:38:42 +0530 Subject: [PATCH 15/30] Merge dynamic role feature & resolve conflicts --- .../org/apache/cloudstack/acl/Permission.java | 22 ------------------- .../cloudstack/acl/ProjectRoleService.java | 1 + .../cloudstack/acl/RolePermissionEntity.java | 3 +++ .../apache/cloudstack/acl/RoleService.java | 1 + .../admin/acl/BaseRolePermissionCmd.java | 2 +- .../admin/acl/UpdateRolePermissionCmd.java | 2 +- .../UpdateProjectRolePermissionCmd.java | 2 +- .../response/BaseRolePermissionResponse.java | 2 +- .../acl/dao/ProjectRolePermissionsDao.java | 2 +- .../dao/ProjectRolePermissionsDaoImpl.java | 2 +- .../acl/dao/RolePermissionsDao.java | 4 ++-- .../acl/dao/RolePermissionsDaoImpl.java | 2 +- .../acl/DynamicRoleBasedAPIAccessChecker.java | 1 + .../DynamicRoleBasedAPIAccessCheckerTest.java | 2 ++ .../acl/ProjectRoleBasedApiAccessChecker.java | 1 + .../acl/ProjectRoleManagerImpl.java | 1 + .../cloudstack/acl/RoleManagerImpl.java | 3 ++- 17 files changed, 21 insertions(+), 32 deletions(-) delete mode 100644 api/src/main/java/org/apache/cloudstack/acl/Permission.java diff --git a/api/src/main/java/org/apache/cloudstack/acl/Permission.java b/api/src/main/java/org/apache/cloudstack/acl/Permission.java deleted file mode 100644 index 032b40ae6cdc..000000000000 --- a/api/src/main/java/org/apache/cloudstack/acl/Permission.java +++ /dev/null @@ -1,22 +0,0 @@ -// 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.acl; - -public enum Permission { - ALLOW, DENY -} diff --git a/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java b/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java index fd8c2961fbac..2bdc9fff96ce 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRolePermissionCmd; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; public interface ProjectRoleService { /** diff --git a/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java index 25abdb3a6ce8..251c6b6d3f9e 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java @@ -21,6 +21,9 @@ import org.apache.cloudstack.api.InternalIdentity; public interface RolePermissionEntity extends InternalIdentity, Identity { + public enum Permission { + ALLOW, DENY + } Rule getRule(); Permission getPermission(); String getDescription(); diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java index 18247c835a67..843b583f4d85 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; public interface RoleService { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java index 40a5d6553dbc..adf514fab0f8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/BaseRolePermissionCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.admin.acl; -import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java index 026533aebed5..9faf1b93a7bf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; import org.apache.cloudstack.acl.RoleType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java index a0a83acf3eb3..b20dc7b5be2b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; import org.apache.cloudstack.acl.RoleType; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java index 33453340bdc8..c39939a20a60 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BaseRolePermissionResponse.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.response; -import org.apache.cloudstack.acl.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java index 115d0534d5dd..dd4b035021be 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDao.java @@ -19,10 +19,10 @@ import java.util.List; -import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRolePermission; import org.apache.cloudstack.acl.ProjectRolePermissionVO; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java index e7db8f54fff2..1eae8f3fa0ea 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java @@ -23,8 +23,8 @@ import java.util.Set; import java.util.stream.Collectors; -import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.ProjectRolePermission; import org.apache.cloudstack.acl.ProjectRolePermissionVO; import org.apache.log4j.Logger; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java index 27af107a844e..d99f8d679486 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDao.java @@ -19,9 +19,9 @@ import java.util.List; -import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; +import org.apache.cloudstack.acl.RolePermissionEntity; import org.apache.cloudstack.acl.RolePermissionVO; import com.cloud.utils.db.GenericDao; @@ -49,7 +49,7 @@ public interface RolePermissionsDao extends GenericDao { * @param permission permission * @return true on success, false if not */ - boolean update(final Role role, final RolePermission rolePermission, final Permission permission); + boolean update(final Role role, final RolePermission rolePermission, final RolePermissionEntity.Permission permission); /** * Returns ordered linked-list of role permission for a given role diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java index b7d8e4bedf12..b63dd502ee7b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java @@ -23,9 +23,9 @@ import java.util.List; import java.util.Set; -import org.apache.cloudstack.acl.Permission; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.RolePermissionVO; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; diff --git a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java index e8dfb6415135..02cdf2a9df4c 100644 --- a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java +++ b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.log4j.Logger; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.UnavailableCommandException; diff --git a/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java b/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java index 48b401fcd6d6..6bae34651260 100644 --- a/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java +++ b/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java @@ -33,6 +33,8 @@ import com.cloud.user.User; import com.cloud.user.UserVO; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; + import junit.framework.TestCase; @RunWith(MockitoJUnitRunner.class) diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 24e493f930ef..5a17bb993eb3 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -21,6 +21,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.context.CallContext; import org.apache.log4j.Logger; diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index 0bd30d771efd..37e7ba70a83f 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.acl.dao.ProjectRoleDao; import org.apache.cloudstack.acl.dao.ProjectRolePermissionsDao; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRoleCmd; import org.apache.cloudstack.api.command.admin.acl.project.CreateProjectRolePermissionCmd; import org.apache.cloudstack.api.command.admin.acl.project.DeleteProjectRoleCmd; diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java index a4f184638f00..8f3c35f105f5 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java @@ -38,6 +38,7 @@ import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd; import org.apache.cloudstack.api.command.admin.acl.UpdateRolePermissionCmd; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -414,7 +415,7 @@ public List findAllPermissionsBy(final Long roleId) { } @Override - public RolePermission.Permission getRolePermission(String permission) { + public Permission getRolePermission(String permission) { if (Strings.isNullOrEmpty(permission)) { return null; } From 8a531d29f74bd33a91be03108c1db1b9621789cf Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 6 Jul 2020 20:26:36 +0530 Subject: [PATCH 16/30] add user access check to projects --- .../cloud/projects/dao/ProjectAccountDao.java | 2 ++ .../projects/dao/ProjectAccountDaoImpl.java | 14 +++++++++++ .../cloud/acl/AffinityGroupAccessChecker.java | 21 +++++++++++++---- .../com/cloud/network/NetworkModelImpl.java | 23 ++++++++++++++++--- .../cloud/projects/ProjectManagerImpl.java | 9 ++++++-- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java index 0e85bb7fbd90..730182a1cb56 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java @@ -33,6 +33,8 @@ public interface ProjectAccountDao extends GenericDao { ProjectAccountVO findByProjectIdUserId(long projectId, long accountId, long userId); + boolean canUserAccessProjectAccount(long accountId, long userId, long projectAccountId); + boolean canAccessProjectAccount(long accountId, long projectAccountId); boolean canModifyProjectAccount(long accountId, long projectAccountId); diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java index e7df24f4e1cf..ea604720a5d0 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java @@ -118,6 +118,20 @@ public ProjectAccountVO findByProjectIdUserId(long projectId, long accountId, lo return findOneBy(sc); } + @Override + public boolean canUserAccessProjectAccount(long accountId, long userId, long projectAccountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("userId", userId); + sc.setParameters("projectAccountId", projectAccountId); + + if (findOneBy(sc) != null) { + return true; + } else { + return false; + } + } + @Override public boolean canAccessProjectAccount(long accountId, long projectAccountId) { SearchCriteria sc = AllFieldsSearch.create(); diff --git a/server/src/main/java/com/cloud/acl/AffinityGroupAccessChecker.java b/server/src/main/java/com/cloud/acl/AffinityGroupAccessChecker.java index 1e9097327c66..6106c7268e13 100644 --- a/server/src/main/java/com/cloud/acl/AffinityGroupAccessChecker.java +++ b/server/src/main/java/com/cloud/acl/AffinityGroupAccessChecker.java @@ -23,15 +23,18 @@ import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; +import org.apache.cloudstack.context.CallContext; import org.springframework.stereotype.Component; import com.cloud.domain.DomainVO; import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.ProjectAccount; import com.cloud.projects.ProjectVO; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.User; import com.cloud.utils.exception.CloudRuntimeException; @Component @@ -75,12 +78,22 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a //acl_type account if (caller.getId() != group.getAccountId()) { //check if the group belongs to a project + User user = CallContext.current().getCallingUser(); ProjectVO project = _projectDao.findByProjectAccountId(group.getAccountId()); + ProjectAccount userProjectAccount = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); if (project != null) { - if (AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canModifyProjectAccount(caller.getId(), group.getAccountId())) { - return true; - } else if (!AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canAccessProjectAccount(caller.getId(), group.getAccountId())) { - return true; + if (userProjectAccount != null) { + if (AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canUserModifyProject(project.getId(), user.getAccountId(), user.getId())) { + return true; + } else if (!AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canUserAccessProjectAccount(user.getAccountId(), user.getId(), group.getAccountId())) { + return true; + } + } else { + if (AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canModifyProjectAccount(caller.getId(), group.getAccountId())) { + return true; + } else if (!AccessType.ModifyProject.equals(accessType) && _projectAccountDao.canAccessProjectAccount(caller.getId(), group.getAccountId())) { + return true; + } } } throw new PermissionDeniedException(caller + " does not have permission to operate with resource " + entity); diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index 031b84331657..54206056c02b 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -34,6 +34,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; @@ -104,11 +105,15 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.projects.dao.ProjectDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; import com.cloud.user.DomainManager; +import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.StringUtils; import com.cloud.utils.component.AdapterBase; @@ -162,6 +167,8 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi PodVlanMapDao _podVlanMapDao; @Inject VpcGatewayDao _vpcGatewayDao; + @Inject + ProjectDao projectDao; private List networkElements; @@ -1649,9 +1656,19 @@ public void checkNetworkPermissions(Account owner, Network network) { throw new PermissionDeniedException("Unable to use network with id= " + ((NetworkVO)network).getUuid() + ", network does not have an owner"); if (owner.getType() != Account.ACCOUNT_TYPE_PROJECT && networkOwner.getType() == Account.ACCOUNT_TYPE_PROJECT) { - if (!_projectAccountDao.canAccessProjectAccount(owner.getAccountId(), network.getAccountId())) { - throw new PermissionDeniedException("Unable to use network with id= " + ((NetworkVO)network).getUuid() + - ", permission denied"); + User user = CallContext.current().getCallingUser(); + Project project = projectDao.findByProjectAccountId(network.getAccountId()); + ProjectAccount projectAccountUser = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); + if (projectAccountUser != null) { + if (!_projectAccountDao.canUserAccessProjectAccount(user.getAccountId(), user.getId(), network.getAccountId())) { + throw new PermissionDeniedException("Unable to use network with id= " + ((NetworkVO)network).getUuid() + + ", permission denied"); + } + } else { + if (!_projectAccountDao.canAccessProjectAccount(owner.getAccountId(), network.getAccountId())) { + throw new PermissionDeniedException("Unable to use network with id= " + ((NetworkVO) network).getUuid() + + ", permission denied"); + } } } else { List networkMap = _networksDao.listBy(owner.getId(), network.getId()); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 615d9350bd1c..1ad8c5da4822 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -542,7 +542,12 @@ public boolean canAccessProjectAccount(Account caller, long accountId) { _accountMgr.checkAccess(caller, _domainDao.findById(owner.getDomainId())); return true; } - + User user = CallContext.current().getCallingUser(); + ProjectVO project = _projectDao.findByProjectAccountId(accountId); + ProjectAccount userProjectAccount = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); + if (userProjectAccount != null) { + return _projectAccountDao.canUserAccessProjectAccount(user.getAccountId(), user.getId(), accountId); + } return _projectAccountDao.canAccessProjectAccount(caller.getId(), accountId); } @@ -562,7 +567,7 @@ public boolean canModifyProjectAccount(Account caller, long accountId) { if (project != null) { ProjectAccountVO projectUser = _projectAccountDao.findByProjectIdUserId(project.getId(), caller.getAccountId(), user.getId()); if (projectUser != null) { - return _projectAccountDao.canUserModifyProject(project.getId(), caller.getId(), user.getId()); + return _projectAccountDao.canUserModifyProject(project.getId(), caller.getAccountId(), user.getId()); } } return _projectAccountDao.canModifyProjectAccount(caller.getId(), accountId); From c127a4655a672620d8be867b505ebe01e652e559 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 8 Jul 2020 08:39:38 +0530 Subject: [PATCH 17/30] Addressed Review comments : Null checks and code formating --- .../command/user/account/AddAccountToProjectCmd.java | 3 ++- .../command/user/account/AddUserToProjectCmd.java | 3 ++- .../api/command/user/project/UpdateProjectCmd.java | 11 ++++------- .../cloudstack/acl/ProjectRolePermissionVO.java | 12 +++++++++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java index 26506b4b0d2c..62cb100d979a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java @@ -38,6 +38,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount; +import com.google.common.base.Strings; @APICommand(name = "addAccountToProject", description = "Adds account to a project", responseObject = SuccessResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -92,7 +93,7 @@ public Long getProjectRoleId() { } public ProjectAccount.Role getRoleType() { - if (roleType != null) { + if (!Strings.isNullOrEmpty(roleType)) { String role = roleType.substring(0, 1).toUpperCase() + roleType.substring(1).toLowerCase(); if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, role)) { throw new InvalidParameterValueException("Only Admin or Regular project role types are valid"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 1587a5555271..a1b900cb6e2e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -36,6 +36,7 @@ import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.projects.ProjectAccount; +import com.google.common.base.Strings; @APICommand(name = AddUserToProjectCmd.APINAME, description = "Adds user to a project", responseObject = SuccessResponse.class, since = "4.14", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) @@ -87,7 +88,7 @@ public Long getProjectRoleId() { } public ProjectAccount.Role getRoleType() { - if (roleType != null) { + if (!Strings.isNullOrEmpty(roleType)) { String role = roleType.substring(0, 1).toUpperCase() + roleType.substring(1).toLowerCase(); if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, role)) { throw new InvalidParameterValueException("Only Admin or Regular project role types are valid"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index 1d1b313900f1..13a131c92781 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -37,6 +37,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount; +import com.google.common.base.Strings; @APICommand(name = "updateProject", description = "Updates a project", responseObject = ProjectResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -109,7 +110,7 @@ public ProjectAccount.Role getRoleType(String role) { } public ProjectAccount.Role getAccountRole() { - if (roleType != null) { + if (!Strings.isNullOrEmpty(roleType)) { return getRoleType(roleType); } return ProjectAccount.Role.Regular; @@ -125,11 +126,7 @@ public String getCommandName() { } public Boolean isSwapOwner() { - if (swapOwner != null) { - return swapOwner; - } else { - return true; - } + return swapOwner != null ? swapOwner : true; } @Override @@ -155,7 +152,7 @@ public List getEntityOwnerIds() { @Override public void execute() throws ResourceAllocationException { CallContext.current().setEventDetails("Project id: " + getId()); - if (getAccountName() != null && (getUserId() != null)) { + if (getAccountName() != null && getUserId() != null) { throw new InvalidParameterValueException("Account name and user ID are mutually exclusive. Provide either account name" + "to update account or user ID to update the user of the project"); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java index 54426308a71e..c700f8478bb9 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java @@ -34,7 +34,9 @@ public class ProjectRolePermissionVO extends RolePermissionBaseVO implements Pro @Column(name = "sort_order") private long sortOrder = 0; - public ProjectRolePermissionVO() { super(); } + public ProjectRolePermissionVO() { + super(); + } public ProjectRolePermissionVO(final long projectId, final long projectRoleId, final String rule, final Permission permission, final String description) { super(rule, permission, description); @@ -43,9 +45,13 @@ public ProjectRolePermissionVO(final long projectId, final long projectRoleId, f } @Override - public long getProjectRoleId() { return projectRoleId; } + public long getProjectRoleId() { + return projectRoleId; + } - public void setProjectRoleId(long projectRoleId) { this.projectRoleId = projectRoleId; } + public void setProjectRoleId(long projectRoleId) { + this.projectRoleId = projectRoleId; + } @Override public long getProjectId() { From ea6e137c2d2327aef446079899500f903d73a422 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 8 Jul 2020 19:15:42 +0530 Subject: [PATCH 18/30] allow username to be taken as input for adding users to projects --- .../com/cloud/projects/ProjectService.java | 2 +- .../CreateProjectRolePermissionCmd.java | 1 + .../ListProjectRolePermissionsCmd.java | 2 +- .../acl/project/ListProjectRolesCmd.java | 2 +- .../admin/acl/project/ProjectRoleCmd.java | 2 +- .../user/account/AddUserToProjectCmd.java | 23 ++++++------- .../ProjectRolePermissionResponse.java | 6 ++-- .../api/response/ProjectRoleResponse.java | 6 ++-- .../main/java/com/cloud/user/dao/UserDao.java | 2 ++ .../java/com/cloud/user/dao/UserDaoImpl.java | 16 ++++++++++ .../api/query/dao/ProjectJoinDaoImpl.java | 32 ++++++++++++++++++- .../cloud/projects/ProjectManagerImpl.java | 11 ++++--- .../acl/ProjectRoleManagerImpl.java | 7 ++++ .../projects/MockProjectManagerImpl.java | 2 +- 14 files changed, 83 insertions(+), 31 deletions(-) diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index 20117cf385ec..ff013481dec6 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -100,6 +100,6 @@ public interface ProjectService { Project findByProjectAccountIdIncludingRemoved(long projectAccountId); - boolean addUserToProject(Long projectId, Long userId, String email, Long projectRoleId, Role projectRole); + boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java index c39cc1d1bf4d..f4fbcf4475a3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java @@ -93,6 +93,7 @@ public long getEntityOwnerId() { private void setupResponse(final ProjectRolePermission rolePermission, final ProjectRole role) { final ProjectRolePermissionResponse response = new ProjectRolePermissionResponse(); response.setId(rolePermission.getUuid()); + response.setProjectId(_projectService.getProject(rolePermission.getProjectId()).getUuid()); response.setProjectRoleId(role.getUuid()); response.setRule(rolePermission.getRule()); response.setRulePermission(rolePermission.getPermission()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java index ca3d1a075c1c..9038f70acf76 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java @@ -89,7 +89,7 @@ public void execute() { private ProjectRolePermissionResponse setupResponse(ProjectRole role, ProjectRolePermission rolePermission) { final ProjectRolePermissionResponse rolePermissionResponse = new ProjectRolePermissionResponse(); - rolePermissionResponse.setProjectId((rolePermission.getProjectId())); + rolePermissionResponse.setProjectId(_projectService.getProject(rolePermission.getProjectId()).getUuid()); rolePermissionResponse.setProjectRoleId(role.getUuid()); rolePermissionResponse.setProjectRoleName(role.getName()); rolePermissionResponse.setId(rolePermission.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java index 0a5886aac024..8c4be4d8a600 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolesCmd.java @@ -97,7 +97,7 @@ public void execute() { private ProjectRoleResponse setupProjectRoleResponse(final ProjectRole role) { final ProjectRoleResponse response = new ProjectRoleResponse(); response.setId(role.getUuid()); - response.setProjectId(role.getProjectId()); + response.setProjectId(_projectService.getProject(role.getProjectId()).getUuid()); response.setRoleName(role.getName()); response.setDescription(role.getDescription()); response.setObjectName("projectrole"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java index 0026ec5e782c..f43cd3d99853 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ProjectRoleCmd.java @@ -52,7 +52,7 @@ public String getProjectRoleDescription() { protected void setupProjectRoleResponse(final ProjectRole role) { final ProjectRoleResponse response = new ProjectRoleResponse(); response.setId(role.getUuid()); - response.setProjectId(role.getProjectId()); + response.setProjectId(_projectService.getProject(role.getProjectId()).getUuid()); response.setRoleName(role.getName()); response.setDescription(role.getDescription()); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index a1b900cb6e2e..388473b6777c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -29,7 +29,6 @@ import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.ProjectRoleResponse; import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; import org.apache.commons.lang3.EnumUtils; @@ -54,9 +53,8 @@ public class AddUserToProjectCmd extends BaseAsyncCmd { description = "ID of the project to add the user to") private Long projectId; - @Parameter(name = ApiConstants.USER_ID, type = BaseCmd.CommandType.UUID, entityType = UserResponse.class, - description = "User UUID, required for adding account from external provisioning system") - private Long userId; + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Name of the user to be added to the project") + private String username; @Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "email ID of user to which invitation to the project is going to be sent") private String email; @@ -77,8 +75,8 @@ public Long getProjectId() { return projectId; } - public Long getUserId() { - return userId; + public String getUsername() { + return username; } public String getEmail() { return email; } @@ -105,7 +103,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Adding user "+getUserId()+" to Project "+getProjectId(); + return "Adding user "+getUsername()+" to Project "+getProjectId(); } ///////////////////////////////////////////////////// @@ -115,7 +113,7 @@ public String getEventDescription() { @Override public void execute() { validateInput(); - boolean result = _projectService.addUserToProject(getProjectId(), getUserId(), getEmail(), getProjectRoleId(), getRoleType()); + boolean result = _projectService.addUserToProject(getProjectId(), getUsername(), getEmail(), getProjectRoleId(), getRoleType()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); @@ -126,18 +124,15 @@ public void execute() { } private void validateInput() { - if (email == null && userId == null) { - throw new InvalidParameterValueException("Must specify atleast userID"); + if (email == null && username == null) { + throw new InvalidParameterValueException("Must specify atleast username"); } - if (email != null && userId == null) { + if (email != null && username == null) { throw new InvalidParameterValueException("Must specify userID for given email ID"); } if (getProjectId() < 1L) { throw new InvalidParameterValueException("Invalid Project ID provided"); } - if (getUserId() < 1L) { - throw new InvalidParameterValueException("Invalid User ID provided"); - } if (projectRoleId != null && projectRoleId < 1L) { throw new InvalidParameterValueException("Invalid Project role ID provided"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java index 0b0638ffcea8..91b2036d3e61 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRolePermissionResponse.java @@ -36,7 +36,7 @@ public class ProjectRolePermissionResponse extends BaseRolePermissionResponse { @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the ID of the project") - private Long projectId; + private String projectId; @SerializedName(ApiConstants.PROJECT_ROLE_NAME) @Param(description = "the name of the project role to which the role permission belongs") @@ -51,11 +51,11 @@ public void setId(String id) { } - public Long getProjectId() { + public String getProjectId() { return projectId; } - public void setProjectId(Long projectId) { + public void setProjectId(String projectId) { this.projectId = projectId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java index f8ee02ac7a3b..230329f0ee13 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectRoleResponse.java @@ -28,13 +28,13 @@ public class ProjectRoleResponse extends BaseRoleResponse { @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the id of the project") - private Long projectId; + private String projectId; - public Long getProjectId() { + public String getProjectId() { return projectId; } - public void setProjectId(Long projectId) { + public void setProjectId(String projectId) { this.projectId = projectId; } } diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java index 6f0f6cf4bf23..14b074251508 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java @@ -27,6 +27,8 @@ public interface UserDao extends GenericDao { UserVO getUser(String username, String password); + UserVO getUserByName(String username, Long domainId); + UserVO getUser(String username); UserVO getUser(long userId); diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java index e15cf803c1d0..8baf732c2406 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java @@ -19,6 +19,8 @@ import java.util.List; +import javax.inject.Inject; + import org.springframework.stereotype.Component; import com.cloud.user.UserVO; @@ -38,6 +40,9 @@ public class UserDaoImpl extends GenericDaoBase implements UserDao protected SearchBuilder SecretKeySearch; protected SearchBuilder RegistrationTokenSearch; + @Inject + private AccountDao accountDao; + protected UserDaoImpl() { UsernameSearch = createSearchBuilder(); UsernameSearch.and("username", UsernameSearch.entity().getUsername(), SearchCriteria.Op.EQ); @@ -77,6 +82,17 @@ public UserVO getUser(String username, String password) { return findOneBy(sc); } + @Override + public UserVO getUserByName(String username, Long domainId) { + List users = findUsersByName(username); + for (UserVO u : users) { + if (accountDao.findActiveAccountById(u.getAccountId(), domainId) != null) { + return u; + } + } + return null; + } + @Override public List listByAccount(long accountId) { SearchCriteria sc = AccountIdSearch.create(); diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java index bedbaaa667ee..3449a883e474 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -35,9 +36,14 @@ import com.cloud.api.query.vo.ProjectJoinVO; import com.cloud.api.query.vo.ResourceTagJoinVO; import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.ProjectAccountVO; +import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.user.Account; +import com.cloud.user.User; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -52,6 +58,10 @@ public class ProjectJoinDaoImpl extends GenericDaoBase impl private AccountJoinDao _accountJoinDao; @Inject private AccountDao _accountDao; + @Inject + private ProjectAccountDao projectAccountDao; + @Inject + private UserDao userDao; private final SearchBuilder prjSearch; @@ -70,6 +80,21 @@ protected ProjectJoinDaoImpl() { this._count = "select count(distinct id) from project_view WHERE "; } + private String getProjectOwners(List projectAccounts) { + StringBuilder owner = new StringBuilder(); + for (ProjectAccount projectAccount : projectAccounts) { + if (owner.length() != 0) { + owner.append(", "); + } + if (projectAccount.getUserId() != null) { + User user = userDao.findById(projectAccount.getUserId()); + owner.append(_accountDao.findById(user.getAccountId()).getAccountName()).append("(").append(user.getUsername()).append(")"); + } else { + owner.append(_accountDao.findById(projectAccount.getAccountId()).getAccountName()); + } + } + return owner.toString(); + } @Override public ProjectResponse newProjectResponse(EnumSet details, ProjectJoinVO proj) { ProjectResponse response = new ProjectResponse(); @@ -82,7 +107,12 @@ public ProjectResponse newProjectResponse(EnumSet details, Projec response.setDomainId(proj.getDomainUuid()); response.setDomain(proj.getDomainName()); - response.setOwner(proj.getOwner()); + List projectAccounts = projectAccountDao.listByProjectId(proj.getId()); + projectAccounts = projectAccounts.stream().filter(projectAccount -> { + return projectAccount.getAccountRole() == ProjectAccount.Role.Admin; + }).collect(Collectors.toList()); + String owners = getProjectOwners(projectAccounts); + response.setOwner(owners); // update tag information List tags = ApiDBUtils.listResourceTagViewByResourceUUID(proj.getUuid(), ResourceObjectType.Project); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 1ad8c5da4822..4c4cbf0d5470 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -462,7 +462,7 @@ public ProjectVO findByProjectAccountIdIncludingRemoved(long projectAccountId) { @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_USER_ADD, eventDescription = "adding user to project", async = true) - public boolean addUserToProject(Long projectId, Long userId, String email, Long projectRoleId, Role projectRole) { + public boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) { Account caller = CallContext.current().getCallingAccount(); Project project = getProject(projectId); @@ -479,10 +479,11 @@ public boolean addUserToProject(Long projectId, Long userId, String email, Long throw ex; } - User user = userDao.findById(userId); + + User user = userDao.getUserByName(username, project.getDomainId()); if (user == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Invalid user ID provided"); - ex.addProxyObject(String.valueOf(userId), "userId"); + ex.addProxyObject(String.valueOf(username), "userId"); throw ex; } @@ -515,10 +516,10 @@ public boolean addUserToProject(Long projectId, Long userId, String email, Long if (_invitationRequired) { return inviteUserToProject(project, user, email, projectRole); } else { - if (userId == null) { + if (username == null) { throw new InvalidParameterValueException("User information (ID) is required to add user to the project"); } - if (assignUserToProject(project, userId, user.getAccountId(), projectRole, + if (assignUserToProject(project, user.getId(), user.getAccountId(), projectRole, Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) { return true; } diff --git a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java index 37e7ba70a83f..90d8e1ecec2c 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -46,6 +46,7 @@ import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; import com.cloud.user.Account; +import com.cloud.user.AccountService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.ListUtils; @@ -68,6 +69,8 @@ public class ProjectRoleManagerImpl extends ManagerBase implements ProjectRoleSe AccountDao accountDao; @Inject ProjectRolePermissionsDao projRolePermissionsDao; + @Inject + AccountService accountService; private static final Logger LOGGER = Logger.getLogger(ProjectRoleManagerImpl.class); @@ -94,6 +97,10 @@ private void checkAccess(Long projectId) { throw new PermissionDeniedException("Restricted API called by an invalid user account"); } + if (accountService.isRootAdmin(callerAcc.getId()) || accountService.isDomainAdmin(callerAcc.getAccountId())) { + return; + } + ProjectAccount projectAccount = projAccDao.findByProjectIdUserId(projectId, callerAcc.getAccountId(), user.getId()); if (projectAccount == null) { projectAccount = projAccDao.findByProjectIdAccountId(projectId, callerAcc.getAccountId()); diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index 999aa7c0e1cb..0dddbeaddab3 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -221,7 +221,7 @@ public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) { } @Override - public boolean addUserToProject(Long projectId, Long userId, String email, Long projectRoleId, Role projectRole) { + public boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) { // TODO Auto-generated method stub return false; } From f10982cca92c973265a0995fcb07971f61ecbc45 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 8 Jul 2020 22:16:12 +0530 Subject: [PATCH 19/30] Update marvin test with correct api parameters --- results.xml | 3 +++ .../smoke/test_enable_role_based_users_in_projects.py | 4 ++-- tools/marvin/marvin/lib/base.py | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 results.xml diff --git a/results.xml b/results.xml new file mode 100644 index 000000000000..9f1d73ba67fc --- /dev/null +++ b/results.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/integration/smoke/test_enable_role_based_users_in_projects.py b/test/integration/smoke/test_enable_role_based_users_in_projects.py index 9a74fc0640bd..16cce8c37cd8 100644 --- a/test/integration/smoke/test_enable_role_based_users_in_projects.py +++ b/test/integration/smoke/test_enable_role_based_users_in_projects.py @@ -171,7 +171,7 @@ def test_add_user_to_project_with_project_role(self): # Add account to the project self.project.addUser( self.apiclient, - userid=self.useraccount.user[0].id, + username=self.useraccount.user[0].username, projectroleid=self.projectrole.id ) Project.listAccounts(self.apiclient, projectid=self.project.id) @@ -226,8 +226,8 @@ def test_add_multiple_admins_in_project(self): account=self.useraccount1.name, projectroleid=self.projectrole.id ) - project_accounts = Project.listAccounts(self.apiclient, projectid=self.project.id, role='Admin') + project_accounts = Project.listAccounts(self.apiclient, projectid=self.project.id, role='Admin') self.assertEqual(len(project_accounts), 2, "account not added with admin Role") self.userapiclientAdminRole = self.testClient.getUserApiClient( diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index e5247238b3b2..10ea666b2de9 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4065,13 +4065,13 @@ def deleteAccount(self, apiclient, account): cmd.account = account return apiclient.deleteAccountFromProject(cmd) - def addUser(self, apiclient, userid=None, email=None, projectroleid=None, roletype=None): + def addUser(self, apiclient, username=None, email=None, projectroleid=None, roletype=None): """Add user to project""" cmd = addUserToProject.addUserToProjectCmd() cmd.projectid = self.id - if userid: - cmd.userid = userid + if username: + cmd.username = username if email: cmd.email = email if projectroleid: From 5274bf72eeebac0c14c2e89849da822b94b23976 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 9 Jul 2020 22:36:50 +0530 Subject: [PATCH 20/30] Added additional fields to listProjects and listProjectAccounts api --- .../com/cloud/projects/ProjectService.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/project/UpdateProjectCmd.java | 30 +-------------- .../api/response/ProjectAccountResponse.java | 8 ++++ .../api/response/ProjectResponse.java | 8 ++++ .../META-INF/db/schema-41400to41500.sql | 1 + .../query/dao/ProjectAccountJoinDaoImpl.java | 1 + .../api/query/dao/ProjectJoinDaoImpl.java | 37 +++++++++---------- .../api/query/vo/ProjectAccountJoinVO.java | 8 ++++ .../cloud/projects/ProjectManagerImpl.java | 20 ++++++++-- .../projects/MockProjectManagerImpl.java | 2 +- 11 files changed, 64 insertions(+), 54 deletions(-) diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index ff013481dec6..4efc000fae5c 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -80,7 +80,7 @@ public interface ProjectService { Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; - Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException; + Project updateProject(long id, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException; boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType); diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index d1622a6d840b..630db737cf8d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -348,6 +348,7 @@ public class ApiConstants { public static final String STORAGE_POLICY = "storagepolicy"; public static final String STORAGE_MOTION_ENABLED = "storagemotionenabled"; public static final String STORAGE_CAPABILITIES = "storagecapabilities"; + public static final String OWNER = "owner"; public static final String SWAP_OWNER = "swapowner"; public static final String SYSTEM_VM_TYPE = "systemvmtype"; public static final String TAGS = "tags"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index 13a131c92781..fa122456cb7f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -24,8 +24,6 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.AccountResponse; -import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; @@ -62,12 +60,6 @@ public class UpdateProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "ID of the user to be promoted/demoted") private Long userId; - @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the user to be promoted/demoted") - private Long domainId; - - @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account owning a project") - private Long accountId; - @Parameter(name = ApiConstants.ROLE_TYPE, type = CommandType.STRING, description = "Account level role to be assigned to the user/account : Admin/Regular") private String roleType; @@ -94,13 +86,6 @@ public Long getUserId() { return userId; } - public Long getDomainId() { - if (domainId != null) { - return domainId; - } - return CallContext.current().getCallingAccount().getDomainId(); - } - public ProjectAccount.Role getRoleType(String role) { String type = role.substring(0, 1).toUpperCase() + role.substring(1).toLowerCase(); if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, type)) { @@ -116,10 +101,6 @@ public ProjectAccount.Role getAccountRole() { return ProjectAccount.Role.Regular; } - public Long getAccountId() { - return accountId; - } - @Override public String getCommandName() { return s_name; @@ -157,20 +138,11 @@ public void execute() throws ResourceAllocationException { "to update account or user ID to update the user of the project"); } - if (getUserId() != null) { - if (getDomainId() == null) { - throw new InvalidParameterValueException("Domain ID needs to be provided when User ID is provided"); - } - if (getAccountId() == null) { - throw new InvalidParameterValueException("Account ID needs to be provided when User ID is provided"); - } - } - Project project = null; if (isSwapOwner()) { project = _projectService.updateProject(getId(), getDisplayText(), getAccountName()); } else { - project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountId(), getDomainId(), getAccountRole()); + project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountRole()); } if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java index 1070cb3274b1..b9f27d0ebb1f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java @@ -45,6 +45,10 @@ public class ProjectAccountResponse extends BaseResponse implements ControlledVi @Param(description = "the name of the account") private String accountName; + @SerializedName(ApiConstants.USERNAME) + @Param(description = "Name of the user") + private String username; + @SerializedName(ApiConstants.ACCOUNT_TYPE) @Param(description = "account type (admin, domain-admin, user)") private Short accountType; @@ -119,4 +123,8 @@ public void setUsers(List users) { public void setRole(String role) { this.role = role; } + + public void setUsername(String username) { + this.username = username; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index 9d9935f85442..4e2497e645ba 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -54,6 +55,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the account name of the project's owner") private String ownerName; + @SerializedName(ApiConstants.OWNER) + @Param(description = "the account name of the project's owners") + private List> owners; + @SerializedName("projectaccountname") @Param(description="the project account name of the project") private String projectAccountName; @@ -421,4 +426,7 @@ public void setSecondaryStorageAvailable(String secondaryStorageAvailable) { this.secondaryStorageAvailable = secondaryStorageAvailable; } + public void setOwners(List> owners) { + this.owners = owners; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index e7e0599e9491..6729ae715dbc 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -137,6 +137,7 @@ ALTER VIEW `cloud`.`project_account_view` AS account.type account_type, user.id user_id, user.uuid user_uuid, + user.username user_name, project_account.account_role, project_role.id project_role_id, project_role.uuid project_role_uuid, diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java index 598c5e1b3707..67da834cefd3 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java @@ -56,6 +56,7 @@ public ProjectAccountResponse newProjectAccountResponse(ProjectAccountJoinVO pro projectAccountResponse.setAccountId(proj.getAccountUuid()); projectAccountResponse.setAccountName(proj.getAccountName()); projectAccountResponse.setUserId(proj.getUserUuid()); + projectAccountResponse.setUsername(proj.getUsername()); projectAccountResponse.setAccountType(proj.getAccountType()); projectAccountResponse.setRole(proj.getAccountRole().toString()); projectAccountResponse.setDomainId(proj.getDomainUuid()); diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java index 3449a883e474..3f384f3c0451 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java @@ -18,7 +18,9 @@ import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @@ -80,21 +82,6 @@ protected ProjectJoinDaoImpl() { this._count = "select count(distinct id) from project_view WHERE "; } - private String getProjectOwners(List projectAccounts) { - StringBuilder owner = new StringBuilder(); - for (ProjectAccount projectAccount : projectAccounts) { - if (owner.length() != 0) { - owner.append(", "); - } - if (projectAccount.getUserId() != null) { - User user = userDao.findById(projectAccount.getUserId()); - owner.append(_accountDao.findById(user.getAccountId()).getAccountName()).append("(").append(user.getUsername()).append(")"); - } else { - owner.append(_accountDao.findById(projectAccount.getAccountId()).getAccountName()); - } - } - return owner.toString(); - } @Override public ProjectResponse newProjectResponse(EnumSet details, ProjectJoinVO proj) { ProjectResponse response = new ProjectResponse(); @@ -108,11 +95,21 @@ public ProjectResponse newProjectResponse(EnumSet details, Projec response.setDomain(proj.getDomainName()); List projectAccounts = projectAccountDao.listByProjectId(proj.getId()); - projectAccounts = projectAccounts.stream().filter(projectAccount -> { - return projectAccount.getAccountRole() == ProjectAccount.Role.Admin; - }).collect(Collectors.toList()); - String owners = getProjectOwners(projectAccounts); - response.setOwner(owners); + projectAccounts = projectAccounts.stream().filter(projectAccount -> projectAccount.getAccountRole() == ProjectAccount.Role.Admin).collect(Collectors.toList()); + List> ownersList = new ArrayList<>(); + for (ProjectAccount projectAccount: projectAccounts) { + Map ownerDetails = new HashMap<>(); + if (projectAccount.getUserId() != null) { + User user = userDao.findById(projectAccount.getUserId()); + ownerDetails.put("account", _accountDao.findById(projectAccount.getAccountId()).getAccountName()); + ownerDetails.put("user", user.getUsername()); + ownerDetails.put("userid", user.getUuid()); + } else { + ownerDetails.put("account", _accountDao.findById(projectAccount.getAccountId()).getAccountName()); + } + ownersList.add(ownerDetails); + } + response.setOwners(ownersList); // update tag information List tags = ApiDBUtils.listResourceTagViewByResourceUUID(proj.getUuid(), ResourceObjectType.Project); diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java index 537d0dc69830..473b24111853 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java @@ -75,6 +75,9 @@ public class ProjectAccountJoinVO extends BaseViewVO implements InternalIdentity @Column(name = "user_id") private Long userId; + @Column(name = "user_name") + private String username; + @Column(name = "project_role_id") private Long projectRoleId; @@ -151,4 +154,9 @@ public String getProjectRoleUuid() { } public String getUserUuid() { return userUuid; } + + public String getUsername() { + return username; + } + } diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 4c4cbf0d5470..d2297d0fac9a 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -194,6 +194,20 @@ private User validateUser(Long userId, Long accountId, Long domainId) { return user; } + private User validateUser(Long userId, Long domainId) { + User user = null; + if (userId != null) { + user = userDao.findById(userId); + if (user == null) { + throw new InvalidParameterValueException("Invalid user ID provided"); + } + if (_accountDao.findById(user.getAccountId()).getDomainId() != domainId) { + throw new InvalidParameterValueException("User doesn't belong to the specified account or domain"); + } + } + return user; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", create = true) @DB @@ -656,7 +670,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Resour @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true) public Project updateProject(final long projectId, final String displayText, final String newOwnerName, Long userId, - Long accountId, Long domainId, Role newRole) throws ResourceAllocationException { + Role newRole) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); //check that the project exists @@ -696,9 +710,9 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Resour } updateProjectAccount(newProjectAcc, newRole, updatedAcc.getId()); } else if (userId != null) { - User user = validateUser(userId, accountId, domainId); + User user = validateUser(userId, project.getDomainId()); if (user == null) { - throw new InvalidParameterValueException("Unable to find user= " + user.getUsername() + " in domain id = " + domainId); + throw new InvalidParameterValueException("Unable to find user= " + user.getUsername() + " in domain id = " + project.getDomainId()); } ProjectAccountVO newProjectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), userId); if (newProjectUser == null) { diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index 0dddbeaddab3..f8af30f2eb2f 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -90,7 +90,7 @@ public Project updateProject(long id, String displayText, String newOwnerName) t } @Override - public Project updateProject(long id, String displayText, String newOwnerName, Long userId, Long accountId, Long domainId, Role newRole) throws ResourceAllocationException { + public Project updateProject(long id, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException { // TODO Auto-generated method stub return null; } From 48cf1cf4e513538b863fa87bee304b41d91346c3 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 21 Jul 2020 20:39:09 +0530 Subject: [PATCH 21/30] Project roles to be applied to resources of a project --- .../cloudstack/context/CallContext.java | 14 +++- .../java/com/cloud/acl/DomainChecker.java | 66 +++++++++++++++++++ .../main/java/com/cloud/api/ApiServlet.java | 1 + 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/context/CallContext.java b/api/src/main/java/org/apache/cloudstack/context/CallContext.java index fb83f8689143..9df02dd320e4 100644 --- a/api/src/main/java/org/apache/cloudstack/context/CallContext.java +++ b/api/src/main/java/org/apache/cloudstack/context/CallContext.java @@ -21,13 +21,12 @@ import java.util.Stack; import java.util.UUID; -import com.cloud.projects.Project; +import org.apache.cloudstack.managed.threadlocal.ManagedThreadLocal; import org.apache.log4j.Logger; import org.apache.log4j.NDC; -import org.apache.cloudstack.managed.threadlocal.ManagedThreadLocal; - import com.cloud.exception.CloudAuthenticationException; +import com.cloud.projects.Project; import com.cloud.user.Account; import com.cloud.user.User; import com.cloud.utils.UuidUtils; @@ -61,6 +60,7 @@ protected Stack initialValue() { private long userId; private final Map context = new HashMap(); private Project project; + private String apiName; static EntityManager s_entityMgr; @@ -335,6 +335,14 @@ public void setProject(Project project) { this.project = project; } + public String getApiName() { + return apiName; + } + + public void setApiName(String apiName) { + this.apiName = apiName; + } + /** * Whether to display the event to the end user. * @return true - if the event is to be displayed to the end user, false otherwise. diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index f864b023c89d..d17c5415b2e9 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -21,8 +21,13 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.ProjectRole; +import org.apache.cloudstack.acl.ProjectRolePermission; +import org.apache.cloudstack.acl.ProjectRoleService; +import org.apache.cloudstack.acl.RolePermissionEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -33,6 +38,7 @@ import com.cloud.domain.Domain; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.UnavailableCommandException; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.vpc.VpcOffering; @@ -41,8 +47,11 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; import com.cloud.projects.ProjectManager; import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.projects.dao.ProjectDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.LaunchPermissionVO; import com.cloud.storage.dao.LaunchPermissionDao; @@ -80,6 +89,12 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { NetworkOfferingDetailsDao networkOfferingDetailsDao; @Inject VpcOfferingDetailsDao vpcOfferingDetailsDao; + @Inject + private ProjectRoleService projectRoleService; + @Inject + private ProjectDao projectDao; + @Inject + private AccountService accountService; public static final Logger s_logger = Logger.getLogger(DomainChecker.class.getName()); protected DomainChecker() { @@ -165,6 +180,7 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a } else if (!_projectMgr.canAccessProjectAccount(caller, account.getId())) { throw new PermissionDeniedException(caller + " does not have permission to operate with resource " + entity); } + checkOperationPermitted(caller, entity); } else { if (caller.getId() != entity.getAccountId()) { throw new PermissionDeniedException(caller + " does not have permission to operate with resource " + entity); @@ -175,6 +191,56 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a return true; } + private boolean checkOperationPermitted(Account caller, ControlledEntity entity) { + User user = CallContext.current().getCallingUser(); + Project project = projectDao.findByProjectAccountId(entity.getAccountId()); + ProjectAccount projectUser = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId()); + String apiCommandName = CallContext.current().getApiName(); + + if (accountService.isRootAdmin(caller.getId()) || accountService.isDomainAdmin(caller.getAccountId())) { + return true; + } + + if (projectUser != null) { + if (projectUser.getAccountRole() == ProjectAccount.Role.Admin) { + return true; + } else { + return isPermitted(project, projectUser, apiCommandName); + } + } + + ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(project.getId(), caller.getAccountId()); + if (projectAccount != null) { + if (projectAccount.getAccountRole() == ProjectAccount.Role.Admin) { + return true; + } else { + return isPermitted(project, projectAccount, apiCommandName); + } + } + throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account/user "); + } + + private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { + ProjectRole projectRole = null; + if(projectUser.getProjectRoleId() != null) { + projectRole = projectRoleService.findProjectRole(projectUser.getProjectRoleId(), project.getId()); + } + + if (projectRole == null) { + return true; + } + + for (ProjectRolePermission permission : projectRoleService.findAllProjectRolePermissions(project.getId(), projectRole.getId())) { + if (permission.getRule().matches(apiCommandName)) { + if (RolePermissionEntity.Permission.ALLOW.equals(permission.getPermission())) { + return true; + } else { + throw new PermissionDeniedException("User/ Account not permitted to access API : "+apiCommandName); + } + } + } + return true; + } @Override public boolean checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { Account account = _accountDao.findById(user.getAccountId()); diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index 9eb12ae9055e..b9f996832672 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -355,6 +355,7 @@ private void setProjectContext(Map requestParameters) { } final String commandName = command[0]; + CallContext.current().setApiName(commandName); for (Map.Entry entry: requestParameters.entrySet()) { if (entry.getKey().equals(ApiConstants.PROJECT_ID) || isSpecificAPI(commandName)) { String projectId = null; From 41b0b5cb53bdf570d56419052cbbaabadc9b8838 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 22 Jul 2020 20:32:25 +0530 Subject: [PATCH 22/30] restrict view of projects --- .../command/user/project/ListProjectsCmd.java | 7 ++++ .../META-INF/db/schema-41400to41500.sql | 30 ++++++++++++++ .../com/cloud/api/query/QueryManagerImpl.java | 39 ++++++++++++++++++- .../com/cloud/api/query/vo/ProjectJoinVO.java | 7 ++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java index db77916d5667..2c965512bba0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java @@ -63,6 +63,9 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { @Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "List projects by tags (key/value pairs)") private Map tags; + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "List projects by username") + private String username; + @Parameter(name = ApiConstants.DETAILS, type = CommandType.LIST, collectionType = CommandType.STRING, @@ -89,6 +92,10 @@ public String getState() { return state; } + public String getUsername() { + return username; + } + @Override public String getCommandName() { return s_name; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index 6729ae715dbc..e9d2167bc445 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -161,6 +161,36 @@ ALTER VIEW `cloud`.`project_account_view` AS left join `cloud`.`user` ON (project_account.user_id = user.id); +DROP VIEW IF EXISTS `cloud`.`project_view`; +CREATE VIEW `cloud`.`project_view` AS + select + projects.id, + projects.uuid, + projects.name, + projects.display_text, + projects.state, + projects.removed, + projects.created, + projects.project_account_id, + account.account_name owner, + pacct.account_id, + pacct.user_id, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path + from + `cloud`.`projects` + inner join + `cloud`.`domain` ON projects.domain_id = domain.id + inner join + `cloud`.`project_account` ON projects.id = project_account.project_id + and project_account.account_role = 'Admin' + inner join + `cloud`.`account` ON account.id = project_account.account_id + left join + `cloud`.`project_account` pacct ON projects.id = pacct.project_id; + -- Fix Debian 10 32-bit hypervisor mappings on VMware, debian10-32bit OS ID in guest_os table is 292, not 282 UPDATE `cloud`.`guest_os_hypervisor` SET guest_os_id=292 WHERE guest_os_id=282 AND hypervisor_type="VMware" AND guest_os_name="debian10Guest"; -- Fix CentOS 32-bit mapping for VMware 5.5 which does not have a centos6Guest but only centosGuest and centos64Guest diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 4bc61f459784..93e7c5a02076 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -227,6 +227,7 @@ import com.cloud.user.DomainManager; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; @@ -416,6 +417,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private ProjectInvitationDao projectInvitationDao; + @Inject + private UserDao userDao; /* * (non-Javadoc) * @@ -1394,6 +1397,7 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd String displayText = cmd.getDisplayText(); String state = cmd.getState(); String accountName = cmd.getAccountName(); + String username = cmd.getUsername(); Long domainId = cmd.getDomainId(); String keyword = cmd.getKeyword(); Long startIndex = cmd.getStartIndex(); @@ -1402,8 +1406,11 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd boolean isRecursive = cmd.isRecursive(); cmd.getTags(); + Account caller = CallContext.current().getCallingAccount(); + User user = CallContext.current().getCallingUser(); Long accountId = null; + Long userId = null; String path = null; Filter searchFilter = new Filter(ProjectJoinVO.class, "id", false, startIndex, pageSize); @@ -1427,11 +1434,23 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd } accountId = owner.getId(); } + if (!Strings.isNullOrEmpty(username)) { + User owner = userDao.getUserByName(username, domainId); + if (owner == null) { + throw new InvalidParameterValueException("Unable to find user " + username + " in domain " + domainId); + } + userId = owner.getId(); + if (accountName == null) { + accountId = owner.getAccountId(); + } + } } else { // domainId == null if (accountName != null) { throw new InvalidParameterValueException("could not find account " + accountName + " because domain is not specified"); } - + if (!Strings.isNullOrEmpty(username)) { + throw new InvalidParameterValueException("could not find user " + username + " because domain is not specified"); + } } } else { if (accountName != null && !accountName.equals(caller.getAccountName())) { @@ -1442,11 +1461,17 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd throw new PermissionDeniedException("Can't list domain id= " + domainId + " projects; unauthorized"); } + if (!Strings.isNullOrEmpty(username) && !username.equals(user.getUsername())) { + throw new PermissionDeniedException("Can't list user " + username + " projects; unauthorized"); + } + accountId = caller.getId(); + userId = user.getId(); } if (domainId == null && accountId == null && (_accountMgr.isNormalUser(caller.getId()) || !listAll)) { accountId = caller.getId(); + userId = user.getId(); } else if (_accountMgr.isDomainAdmin(caller.getId()) || (isRecursive && !listAll)) { DomainVO domain = _domainDao.findById(caller.getDomainId()); path = domain.getPath(); @@ -1460,6 +1485,14 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ); } + if (userId != null) { + sb.and().op("userId", sb.entity().getUserId(), Op.EQ); + sb.or("userIdNull", sb.entity().getUserId(), Op.NULL); + sb.cp(); + } else { + sb.and("userIdNull", sb.entity().getUserId(), Op.NULL); + } + SearchCriteria sc = sb.create(); if (id != null) { @@ -1482,6 +1515,10 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd sc.setParameters("accountId", accountId); } + if (userId != null) { + sc.setParameters("userId", userId); + } + if (state != null) { sc.addAnd("state", Op.EQ, state); } diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectJoinVO.java index b17c9ff5915f..4c7b6243374e 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectJoinVO.java @@ -76,6 +76,9 @@ public class ProjectJoinVO extends BaseViewVO implements InternalIdentity, Ident @Column(name = "domain_path") private String domainPath; + @Column(name = "user_id") + private long userId; + @Column(name = "project_account_id") private long projectAccountId; @@ -139,4 +142,8 @@ public long getAccountId() { public long getProjectAccountId() { return projectAccountId; } + + public long getUserId() { + return userId; + } } From 318b1c8d4f2a8aacf24c525a310d858f793383b2 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 23 Jul 2020 12:01:07 +0530 Subject: [PATCH 23/30] Handle verification of project roles when apikey is passed --- .../api/command/user/account/AddUserToProjectCmd.java | 2 +- server/src/main/java/com/cloud/api/ApiServlet.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 388473b6777c..c8e71feb5797 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -128,7 +128,7 @@ private void validateInput() { throw new InvalidParameterValueException("Must specify atleast username"); } if (email != null && username == null) { - throw new InvalidParameterValueException("Must specify userID for given email ID"); + throw new InvalidParameterValueException("Must specify username for given email ID"); } if (getProjectId() < 1L) { throw new InvalidParameterValueException("Invalid Project ID provided"); diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index b9f996832672..a660f8660163 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -312,6 +312,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp // Add the HTTP method (GET/POST/PUT/DELETE) as well into the params map. params.put("httpmethod", new String[]{req.getMethod()}); + setProjectContext(params); final String response = apiServer.handleRequest(params, responseType, auditTrailSb); HttpUtils.writeHttpResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType, ApiServer.JSONcontentType.value()); } else { From a979dd031c2883ca423c807d620ba9fbe3f48a31 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 27 Jul 2020 13:28:45 +0530 Subject: [PATCH 24/30] vague error message --- server/src/main/java/com/cloud/acl/DomainChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index d17c5415b2e9..f6c39a1e7f00 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -235,7 +235,7 @@ private boolean isPermitted(Project project, ProjectAccount projectUser, String if (RolePermissionEntity.Permission.ALLOW.equals(permission.getPermission())) { return true; } else { - throw new PermissionDeniedException("User/ Account not permitted to access API : "+apiCommandName); + throw new PermissionDeniedException(apiCommandName + " does not exist"); } } } From 296e4bd9cff53033b161b903bfc64fb75e978818 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 27 Jul 2020 18:03:17 +0530 Subject: [PATCH 25/30] error message description consistency --- server/src/main/java/com/cloud/acl/DomainChecker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index f6c39a1e7f00..1077d2bc4803 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -217,7 +217,7 @@ private boolean checkOperationPermitted(Account caller, ControlledEntity entity) return isPermitted(project, projectAccount, apiCommandName); } } - throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account/user "); + throw new UnavailableCommandException("The given command '" + apiCommandName + "' either does not exist or is not available for the user"); } private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { @@ -235,7 +235,7 @@ private boolean isPermitted(Project project, ProjectAccount projectUser, String if (RolePermissionEntity.Permission.ALLOW.equals(permission.getPermission())) { return true; } else { - throw new PermissionDeniedException(apiCommandName + " does not exist"); + throw new PermissionDeniedException("The given command '" + apiCommandName + "' either does not exist or is not available for the user"); } } } From 5293c32f891b95c8ff87a2e6da1fc871f38fe9d7 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 28 Jul 2020 13:17:32 +0530 Subject: [PATCH 26/30] Allow project roles with same names across projects --- .../src/main/resources/META-INF/db/schema-41400to41500.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index e9d2167bc445..de9120ff2642 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`project_role` ( `project_id` bigint(20) unsigned COMMENT 'Id of the project to which the role belongs', PRIMARY KEY (`id`), KEY `i_project_role__name` (`name`), - UNIQUE KEY (`name`), + UNIQUE KEY (`name`, `project_id`), CONSTRAINT `fk_project_role__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; From 76fe592abc036ef49e599803e72caa78f21d43c8 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 28 Jul 2020 19:08:59 +0530 Subject: [PATCH 27/30] sql changes for upgrade path --- .../META-INF/db/schema-41400to41500.sql | 61 ++++++------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index de9120ff2642..f9940b8587c3 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -52,49 +52,25 @@ CREATE TABLE IF NOT EXISTS `cloud`.`project_role_permissions` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Alter project accounts table to include user_id and project_role_id for role based users in projects -DROP TABLE IF EXISTS `cloud`.`project_account`; -CREATE TABLE `cloud`.`project_account` ( - `id` bigint unsigned NOT NULL auto_increment, - `account_id` bigint unsigned NOT NULL COMMENT'account id', - `user_id` bigint unsigned COMMENT 'ID of user to be added to the project', - `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)', - `project_id` bigint unsigned NOT NULL COMMENT 'project id', - `project_account_id` bigint unsigned NOT NULL, - `project_role_id` bigint unsigned COMMENT 'Project role id', - `created` datetime COMMENT 'date created', - PRIMARY KEY (`id`), - CONSTRAINT `fk_project_account__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_account__user_id` FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_account__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_account__project_account_id` FOREIGN KEY(`project_account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`), - UNIQUE (`account_id`, `user_id`, `project_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +ALTER TABLE `cloud`.`project_account` + ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, + ADD CONSTRAINT `fk_project_account__user_id` FOREIGN KEY `fk_project_account__user_id`(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `project_account_id`, + ADD CONSTRAINT `fk_project_account__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`) ON DELETE SET NULL, + DROP FOREIGN KEY `fk_project_account__account_id`, + DROP INDEX `account_id`; + +ALTER TABLE `cloud`.`project_account` + ADD CONSTRAINT `fk_project_account__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE , + ADD CONSTRAINT `uc_project_account__project_id_account_id_user_id` UNIQUE (`project_id`, `account_id`, `user_id`) ; -- Alter project invitations table to include user_id for invites sent to specific users of an account -DROP TABLE IF EXISTS `cloud`.`project_invitations`; -CREATE TABLE `cloud`.`project_invitations` ( - `id` bigint unsigned NOT NULL auto_increment, - `uuid` varchar(40), - `project_id` bigint unsigned NOT NULL COMMENT 'project id', - `account_id` bigint unsigned COMMENT 'account id', - `user_id` bigint unsigned COMMENT 'ID of user to be added to the project', - `domain_id` bigint unsigned COMMENT 'domain id', - `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)', - `email` varchar(255) COMMENT 'email', - `token` varchar(255) COMMENT 'token', - `state` varchar(255) NOT NULL DEFAULT 'Pending' COMMENT 'the state of the invitation', - `created` datetime COMMENT 'date created', - PRIMARY KEY (`id`), - CONSTRAINT `fk_project_invitations__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_invitations__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE, - CONSTRAINT `fk_project_invitations__project_id` FOREIGN KEY(`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE, - UNIQUE (`project_id`, `account_id`,`user_id`), - UNIQUE (`project_id`, `email`), - UNIQUE (`project_id`, `token`), - CONSTRAINT `uc_project_invitations__uuid` UNIQUE (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +ALTER TABLE `cloud`.`project_invitations` + ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, + ADD COLUMN `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)' AFTER `domain_id`, + ADD CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + DROP INDEX `project_id`, + ADD CONSTRAINT `uc_project_invitations__project_id_account_id_user_id` UNIQUE (`project_id`, `account_id`,`user_id`); -- Alter project_invitation_view to incroporate user_id as a field ALTER VIEW `cloud`.`project_invitation_view` AS @@ -161,8 +137,7 @@ ALTER VIEW `cloud`.`project_account_view` AS left join `cloud`.`user` ON (project_account.user_id = user.id); -DROP VIEW IF EXISTS `cloud`.`project_view`; -CREATE VIEW `cloud`.`project_view` AS +ALTER VIEW `cloud`.`project_view` AS select projects.id, projects.uuid, From 826f2e71b424a18bea6ee5948bfc036dad3a65d1 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 10 Aug 2020 09:35:49 +0530 Subject: [PATCH 28/30] Code refactor --- .../com/cloud/projects/ProjectInvitation.java | 2 + .../project/UpdateProjectInvitationCmd.java | 11 ++-- .../cloud/projects/ProjectInvitationVO.java | 12 +++++ .../META-INF/db/schema-41400to41500.sql | 5 +- .../cloud/projects/ProjectManagerImpl.java | 51 ++++++++++++------- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/api/src/main/java/com/cloud/projects/ProjectInvitation.java b/api/src/main/java/com/cloud/projects/ProjectInvitation.java index 61d64cc4695d..7a8d338694f2 100644 --- a/api/src/main/java/com/cloud/projects/ProjectInvitation.java +++ b/api/src/main/java/com/cloud/projects/ProjectInvitation.java @@ -45,4 +45,6 @@ public enum State { Long getForUserId(); + Long getProjectRoleId(); + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java index e8de2efff8c4..53fccfed1a57 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java @@ -16,22 +16,20 @@ // under the License. package org.apache.cloudstack.api.command.user.project; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.response.UserResponse; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; import com.cloud.event.EventTypes; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; @APICommand(name = "updateProjectInvitation", description = "Accepts or declines project invitation", responseObject = SuccessResponse.class, since = "3.0.0", @@ -102,9 +100,6 @@ public long getEntityOwnerId() { @Override public void execute() { - if (accountName != null && userId != null) { - throw new InvalidParameterValueException("Provide either accountName or User ID to updating the invite"); - } String eventDetails = "Project id: " + projectId + ";"; if (accountName != null) { eventDetails += " accountName: " + accountName + ";"; diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java index ce59b2503a8d..4f794a831629 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java @@ -51,6 +51,9 @@ public class ProjectInvitationVO implements ProjectInvitation { @Enumerated(value = EnumType.STRING) private ProjectAccount.Role accountRole = ProjectAccount.Role.Regular; + @Column(name = "project_role_id") + private Long projectRoleId; + @Column(name = "token") private String token; @@ -143,6 +146,15 @@ public void setAccountRole(ProjectAccount.Role accountRole) { this.accountRole = accountRole; } + @Override + public Long getProjectRoleId() { + return projectRoleId; + } + + public void setProjectRoleId(Long projectRoleId) { + this.projectRoleId = projectRoleId; + } + @Override public String getUuid() { return uuid; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index f9940b8587c3..d0a5727b37e8 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -68,11 +68,13 @@ ALTER TABLE `cloud`.`project_account` ALTER TABLE `cloud`.`project_invitations` ADD COLUMN `user_id` bigint unsigned COMMENT 'ID of user to be added to the project' AFTER `account_id`, ADD COLUMN `account_role` varchar(255) NOT NULL DEFAULT 'Regular' COMMENT 'Account role in the project (Owner or Regular)' AFTER `domain_id`, + ADD COLUMN `project_role_id` bigint unsigned COMMENT 'Project role id' AFTER `account_role`, ADD CONSTRAINT `fk_project_invitations__user_id` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + ADD CONSTRAINT `fk_project_invitations__project_role_id` FOREIGN KEY (`project_role_id`) REFERENCES `project_role` (`id`) ON DELETE SET NULL, DROP INDEX `project_id`, ADD CONSTRAINT `uc_project_invitations__project_id_account_id_user_id` UNIQUE (`project_id`, `account_id`,`user_id`); --- Alter project_invitation_view to incroporate user_id as a field +-- Alter project_invitation_view to incorporate user_id as a field ALTER VIEW `cloud`.`project_invitation_view` AS select project_invitations.id, @@ -80,6 +82,7 @@ ALTER VIEW `cloud`.`project_invitation_view` AS project_invitations.email, project_invitations.created, project_invitations.state, + project_invitations.project_role_id, projects.id project_id, projects.uuid project_uuid, projects.name project_name, diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index d2297d0fac9a..90a27fcafd01 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -528,7 +528,7 @@ public boolean addUserToProject(Long projectId, String username, String email, L } if (_invitationRequired) { - return inviteUserToProject(project, user, email, projectRole); + return inviteUserToProject(project, user, email, projectRole, role); } else { if (username == null) { throw new InvalidParameterValueException("User information (ID) is required to add user to the project"); @@ -795,7 +795,7 @@ public boolean addAccountToProject(long projectId, String accountName, String em } if (_invitationRequired) { - return inviteAccountToProject(project, account, email, projectRoleType); + return inviteAccountToProject(project, account, email, projectRoleType, projectRole); } else { if (account == null) { throw new InvalidParameterValueException("Account information is required for assigning account to the project"); @@ -810,9 +810,10 @@ public boolean addAccountToProject(long projectId, String accountName, String em } } - private boolean inviteAccountToProject(Project project, Account account, String email, Role role) { + private boolean inviteAccountToProject(Project project, Account account, String email, Role role,ProjectRole projectRole) { if (account != null) { - if (createAccountInvitation(project, account.getId(), null, role) != null) { + if (createAccountInvitation(project, account.getId(), null, role, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to generate invitation for account " + account.getAccountName() + " to project id=" + project); @@ -823,7 +824,8 @@ private boolean inviteAccountToProject(Project project, Account account, String if (email != null) { //generate the token String token = generateToken(10); - if (generateTokenBasedInvitation(project, null, email, token, role) != null) { + if (generateTokenBasedInvitation(project, null, email, token, role, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to generate invitation for email " + email + " to project id=" + project); @@ -834,9 +836,10 @@ private boolean inviteAccountToProject(Project project, Account account, String return false; } - private boolean inviteUserToProject(Project project, User user, String email, Role role) { + private boolean inviteUserToProject(Project project, User user, String email, Role role, ProjectRole projectRole) { if (email == null) { - if (createAccountInvitation(project, user.getAccountId(), user.getId(), role) != null) { + if (createAccountInvitation(project, user.getAccountId(), user.getId(), role, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to generate invitation for account " + user.getUsername() + " to project id=" + project); @@ -845,7 +848,8 @@ private boolean inviteUserToProject(Project project, User user, String email, Ro } else { //generate the token String token = generateToken(10); - if (generateTokenBasedInvitation(project, user.getId(), email, token, role) != null) { + if (generateTokenBasedInvitation(project, user.getId(), email, token, role, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to generate invitation for email " + email + " to project id=" + project); @@ -994,7 +998,7 @@ public Boolean doInTransaction(TransactionStatus status) { }); } - public ProjectInvitation createAccountInvitation(Project project, Long accountId, Long userId, Role role) { + public ProjectInvitation createAccountInvitation(Project project, Long accountId, Long userId, Role role, Long projectRoleId) { if (activeInviteExists(project, accountId, userId, null)) { throw new InvalidParameterValueException("There is already a pending invitation for account id=" + accountId + " to the project id=" + project); } @@ -1006,6 +1010,9 @@ public ProjectInvitation createAccountInvitation(Project project, Long accountId if (role != null) { invitationVO.setAccountRole(role); } + if (projectRoleId != null) { + invitationVO.setProjectRoleId(projectRoleId); + } return _projectInvitationDao.persist(invitationVO); } @@ -1050,7 +1057,7 @@ public Boolean doInTransaction(TransactionStatus status) { }); } - public ProjectInvitation generateTokenBasedInvitation(Project project, Long userId, String email, String token, Role role) { + public ProjectInvitation generateTokenBasedInvitation(Project project, Long userId, String email, String token, Role role, Long projectRoleId) { //verify if the invitation was already generated if (activeInviteExists(project, null, null, email)) { throw new InvalidParameterValueException("There is already a pending invitation for email " + email + " to the project id=" + project); @@ -1063,6 +1070,10 @@ public ProjectInvitation generateTokenBasedInvitation(Project project, Long user if (role != null) { projectInvitationVO.setAccountRole(role); } + if (projectRoleId != null) { + projectInvitationVO.setProjectRoleId(projectRoleId); + } + ProjectInvitation projectInvitation = _projectInvitationDao.persist(projectInvitationVO); try { _emailInvite.sendInvite(token, email, project.getId()); @@ -1107,18 +1118,24 @@ public boolean updateInvitation(final long projectId, String accountName, Long u //verify permissions _accountMgr.checkAccess(caller, null, true, account); accountId = account.getId(); - } else { + } else if (userId != null) { user = userDao.findById(userId); if (user == null) { throw new InvalidParameterValueException("Invalid user ID provided. Please provide a valid user ID or " + "account name whose invitation is to be updated"); } + Account userAccount = _accountDao.findById(user.getAccountId()); + if (userAccount.getDomainId() != project.getDomainId()) { + throw new InvalidParameterValueException("Unable to find user =" + userId + " in domain id=" + project.getDomainId()); + } + } else { + accountId = caller.getId(); + user = CallContext.current().getCallingUser(); } //check that invitation exists ProjectInvitationVO invite = null; if (token == null) { - if (accountName != null) { invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); } else { @@ -1152,19 +1169,19 @@ public Boolean doInTransaction(TransactionStatus status) { if (result && accept) { //check if account already exists for the project (was added before invitation got accepted) - if (accountName != null) { + if (finalInvite.getForUserId() == -1) { ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountIdFinal); if (projectAccount != null) { s_logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); } else { - assignAccountToProject(project, accountIdFinal, finalInvite.getAccountRole(), null, null); + assignAccountToProject(project, accountIdFinal, finalInvite.getAccountRole(), null, finalInvite.getProjectRoleId()); } } else { - ProjectAccount projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, finalUser.getAccountId(), userId); + ProjectAccount projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, finalUser.getAccountId(), finalUser.getId()); if (projectAccount != null) { - s_logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); + s_logger.debug("User " + finalUser.getId() + "has already been added to the project id=" + projectId); } else { - assignUserToProject(project, finalInvite.getForUserId(), finalUser.getAccountId(), finalInvite.getAccountRole(), null); + assignUserToProject(project, finalInvite.getForUserId(), finalUser.getAccountId(), finalInvite.getAccountRole(), finalInvite.getProjectRoleId()); } } } else { From 95a6a4311dd59d95138bda38c5f4c98adc78f699 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 10 Aug 2020 19:49:36 +0530 Subject: [PATCH 29/30] remove results.xml file --- results.xml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 results.xml diff --git a/results.xml b/results.xml deleted file mode 100644 index 9f1d73ba67fc..000000000000 --- a/results.xml +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file From 970acd0d09923c6d56ee8d27a9566f0504323a6b Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 11 Aug 2020 13:59:17 +0530 Subject: [PATCH 30/30] detailed description of api parameter --- .../cloudstack/api/command/user/project/UpdateProjectCmd.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index fa122456cb7f..70aa26d83384 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -63,7 +63,9 @@ public class UpdateProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ROLE_TYPE, type = CommandType.STRING, description = "Account level role to be assigned to the user/account : Admin/Regular") private String roleType; - @Parameter(name = ApiConstants.SWAP_OWNER, type = CommandType.BOOLEAN, description = "True by default; when true, makes provided account the owner") + @Parameter(name = ApiConstants.SWAP_OWNER, type = CommandType.BOOLEAN, description = "when true, it swaps ownership with the account/ user provided. " + + "Ideally to be used when a single project administrator is present. In case of multiple project admins, swapowner is to be set to false," + + "to promote or demote the user/account based on the roleType (Regular or Admin) provided. Defaults to true") private Boolean swapOwner; /////////////////////////////////////////////////////