diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 5ea31a781786..d14719247c17 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 { @@ -190,6 +190,14 @@ 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 = "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"; @@ -399,9 +407,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/ProjectInvitation.java b/api/src/main/java/com/cloud/projects/ProjectInvitation.java index ee2822290341..7a8d338694f2 100644 --- a/api/src/main/java/com/cloud/projects/ProjectInvitation.java +++ b/api/src/main/java/com/cloud/projects/ProjectInvitation.java @@ -41,4 +41,10 @@ public enum State { Long getInDomainId(); + ProjectAccount.Role getAccountRole(); + + Long getForUserId(); + + Long getProjectRoleId(); + } diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index dc882ef11b8d..4efc000fae5c 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,10 +66,12 @@ 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); @@ -69,11 +80,15 @@ public interface ProjectService { Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException; - boolean addAccountToProject(long projectId, String accountName, String email); + 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); boolean deleteAccountFromProject(long projectId, String accountName); - boolean updateInvitation(long projectId, String accountName, String token, boolean accept); + boolean deleteUserFromProject(long projectId, long userId); + + boolean updateInvitation(long projectId, String accountName, Long userId, String token, boolean accept); Project activateProject(long projectId); @@ -84,4 +99,7 @@ public interface ProjectService { boolean deleteProjectInvitation(long invitationId); Project findByProjectAccountIdIncludingRemoved(long projectAccountId); + + boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole); + } 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..601c1684d264 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRole.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 ProjectRole extends RoleEntity, InternalIdentity, Identity { + + Long getProjectId(); +} 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..2bdc9fff96ce --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/ProjectRoleService.java @@ -0,0 +1,118 @@ +// 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; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; + +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 a31ec524c526..c25d7a99867c 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/Role.java +++ b/api/src/main/java/org/apache/cloudstack/acl/Role.java @@ -20,9 +20,7 @@ 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(); boolean isDefault(); } 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..251c6b6d3f9e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java @@ -0,0 +1,30 @@ +// 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 { + 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 2e181dcf3f2a..dfc5cb1cafe7 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -22,7 +22,7 @@ import com.cloud.utils.Pair; -import org.apache.cloudstack.acl.RolePermission.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity.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 073c94049106..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,8 @@ 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"; public static final String TARGET_IQN = "targetiqn"; @@ -474,12 +476,15 @@ 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 PROJECT_ROLE_ID = "projectroleid"; + public static final String PROJECT_ROLE_NAME = "projectrolename"; 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 RULES = "rules"; public static final String RULE_ID = "ruleid"; + 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/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index d00112b91dbe..1f6d3d503758 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/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..adf514fab0f8 --- /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.RolePermissionEntity.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..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 @@ -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.RolePermissionEntity.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..a3fa14955c15 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java @@ -0,0 +1,80 @@ +// 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.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, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}, 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..f4fbcf4475a3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java @@ -0,0 +1,105 @@ +// 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.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.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, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, 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.setProjectId(_projectService.getProject(rolePermission.getProjectId()).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..c1a4d3912511 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.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.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, RoleType.User}) +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..17e74824102f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.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.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; +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, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, 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..9038f70acf76 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/ListProjectRolePermissionsCmd.java @@ -0,0 +1,113 @@ +// 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.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.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, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, 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(_projectService.getProject(rolePermission.getProjectId()).getUuid()); + 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("projectrolepermission"); + 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..8c4be4d8a600 --- /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, RoleType.User}) +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(_projectService.getProject(role.getProjectId()).getUuid()); + 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..f43cd3d99853 --- /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(_projectService.getProject(role.getProjectId()).getUuid()); + 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..41f68645e9b2 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java @@ -0,0 +1,85 @@ +// 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.ProjectRoleResponse; + +@APICommand(name = UpdateProjectRoleCmd.APINAME, description = "Creates a Project role", responseObject = ProjectRoleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}, 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..b20dc7b5be2b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java @@ -0,0 +1,163 @@ +// 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.RolePermissionEntity.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; +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, authorized = { + RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, 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_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") + 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..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 @@ -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,8 @@ import com.cloud.event.EventTypes; 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) @@ -56,6 +64,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; default: Regular") + private String roleType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -72,6 +88,21 @@ public String getEmail() { return email; } + public Long getProjectRoleId() { + return projectRoleId; + } + + public ProjectAccount.Role getRoleType() { + 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"); + } + return Enum.valueOf(ProjectAccount.Role.class, role); + } + return null; + } + @Override public String getCommandName() { return s_name; @@ -88,7 +119,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 +141,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..c8e71feb5797 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -0,0 +1,150 @@ +// 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.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.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.context.CallContext; +import org.apache.commons.lang3.EnumUtils; + +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}) +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.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; + + @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; + + @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 /////////////////////// + ///////////////////////////////////////////////////// + + public Long getProjectId() { + return projectId; + } + + public String getUsername() { + return username; + } + + public String getEmail() { return email; } + + public Long getProjectRoleId() { + return projectRoleId; + } + + public ProjectAccount.Role getRoleType() { + 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"); + } + return Enum.valueOf(ProjectAccount.Role.class, role); + } + return null; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_PROJECT_USER_ADD; + } + + @Override + public String getEventDescription() { + return "Adding user "+getUsername()+" to Project "+getProjectId(); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + validateInput(); + boolean result = _projectService.addUserToProject(getProjectId(), getUsername(), getEmail(), 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 (email == null && username == null) { + throw new InvalidParameterValueException("Must specify atleast username"); + } + if (email != null && username == null) { + throw new InvalidParameterValueException("Must specify username for given email ID"); + } + if (getProjectId() < 1L) { + throw new InvalidParameterValueException("Invalid Project ID provided"); + } + if (projectRoleId != null && projectRoleId < 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..38cb5a2011c7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java @@ -0,0 +1,118 @@ +// 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.acl.RoleType; +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, 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"; + + ///////////////////////////////////////////////////// + //////////////// 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/account/ListProjectAccountsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java index 3fcb1989fa56..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,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.account; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListCmd; @@ -25,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; @@ -45,9 +46,15 @@ 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; + @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 /////////////////////// ///////////////////////////////////////////////////// @@ -64,6 +71,12 @@ public String getRole() { return role; } + 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/command/user/project/ActivateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java index 5b1b76e95eaa..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 @@ -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; @@ -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..40db387debed 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; } @@ -96,10 +112,13 @@ public String getCommandName() { public long getEntityOwnerId() { Account caller = CallContext.current().getCallingAccount(); - if ((accountName != null && domainId == null) || (domainId != null && accountName == null)) { + if ((accountName != null && domainId == null)) { throw new InvalidParameterValueException("Account name and domain id must be specified together"); } + if (userId != null && (accountId == null && domainId == null)) { + throw new InvalidParameterValueException("Account ID and Domain ID must be specified with userID"); + } if (accountName != null) { return _accountService.finalizeOwner(caller, accountName, domainId, null).getId(); } @@ -126,7 +145,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..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 @@ -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; @@ -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/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/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/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..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 @@ -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; @@ -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..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 @@ -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; @@ -25,12 +25,17 @@ 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.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; +import com.google.common.base.Strings; @APICommand(name = "updateProject", description = "Updates a project", responseObject = ProjectResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -52,6 +57,17 @@ 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.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 = "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; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,11 +84,34 @@ public String getDisplayText() { return displayText; } + public Long getUserId() { + return userId; + } + + 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() { + if (!Strings.isNullOrEmpty(roleType)) { + return getRoleType(roleType); + } + return ProjectAccount.Role.Regular; + } + @Override public String getCommandName() { return s_name; } + public Boolean isSwapOwner() { + return swapOwner != null ? swapOwner : true; + } + @Override public long getEntityOwnerId() { Project project = _projectService.getProject(id); @@ -84,6 +123,11 @@ public long getEntityOwnerId() { return _projectService.getProjectOwner(id).getId(); } + @Override + public List getEntityOwnerIds() { + return _projectService.getProjectOwners(id); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -91,7 +135,17 @@ 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"); + } + + Project project = null; + if (isSwapOwner()) { + project = _projectService.updateProject(getId(), getDisplayText(), getAccountName()); + } else { + project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountRole()); + } if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); response.setResponseName(getCommandName()); 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 79e3f8a9cfdd..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,17 +16,18 @@ // under the License. package org.apache.cloudstack.api.command.user.project; -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.user.Account; @@ -46,6 +47,10 @@ public class UpdateProjectInvitationCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "account that is joining the project") private String accountName; + @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.TOKEN, type = CommandType.STRING, description = "list invitations for specified account; this parameter has to be specified with domainId") @@ -65,6 +70,8 @@ public String getAccountName() { return accountName; } + public Long getUserId() { return userId; } + @Override public String getCommandName() { return s_name; @@ -93,8 +100,15 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Project id: " + projectId + "; accountName " + accountName + "; accept " + getAccept()); - boolean result = _projectService.updateInvitation(projectId, accountName, token, getAccept()); + String eventDetails = "Project id: " + projectId + ";"; + if (accountName != null) { + eventDetails += " accountName: " + accountName + ";"; + } else if (userId != null) { + eventDetails += " userId: " + userId + ";"; + } + eventDetails += " accept " + getAccept(); + CallContext.current().setEventDetails(eventDetails); + boolean result = _projectService.updateInvitation(projectId, accountName, userId, token, getAccept()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); 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..c39939a20a60 --- /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.RolePermissionEntity.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/ProjectAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java index 7afad59096b8..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 @@ -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") @@ -46,10 +45,22 @@ 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; + @SerializedName(ApiConstants.USER_ID) + @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; @@ -99,6 +110,12 @@ public void setDomainName(String domainName) { this.domainName = 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; } @@ -106,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/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/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..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,8 +18,7 @@ import java.util.ArrayList; import java.util.List; - -import com.google.gson.annotations.SerializedName; +import java.util.Map; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -27,6 +26,7 @@ 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 { @@ -55,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; @@ -422,4 +426,7 @@ public void setSecondaryStorageAvailable(String secondaryStorageAvailable) { this.secondaryStorageAvailable = secondaryStorageAvailable; } + public void setOwners(List> owners) { + this.owners = owners; + } } 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..91b2036d3e61 --- /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 String 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 String getProjectId() { + return projectId; + } + + public void setProjectId(String 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..230329f0ee13 --- /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 String projectId; + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String 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 88154fa3e164..1861028f0ed5 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,54 +17,31 @@ 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; - @SerializedName(ApiConstants.IS_DEFAULT) @Param(description = "true if role is default, false otherwise") private Boolean isDefault; - 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; - } - public void setIsDefault(Boolean isDefault) { this.isDefault = isDefault; } 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/client/pom.xml b/client/pom.xml index 0d76c9f2ae33..7b9808ba6fc4 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -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/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java index 8a337d620d2a..4710a815f978 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,26 @@ 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; + if (accountRole != null) { + this.accountRole = accountRole; + } else { + this.accountRole = Role.Regular; + } this.projectId = project.getId(); this.projectAccountId = project.getProjectAccountId(); + this.userId = userId; + this.projectRoleId = projectRoleId; } @Override @@ -81,6 +93,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 +110,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/ProjectInvitationVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java index 9775135beabb..4f794a831629 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java @@ -47,6 +47,13 @@ public class ProjectInvitationVO implements ProjectInvitation { @Column(name = "domain_id") private Long inDomainId; + @Column(name = "account_role") + @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; @@ -63,6 +70,9 @@ public class ProjectInvitationVO implements ProjectInvitation { @Column(name = "uuid") private String uuid; + @Column(name = "user_id") + private Long forUserId; + protected ProjectInvitationVO() { uuid = UUID.randomUUID().toString(); } @@ -127,6 +137,24 @@ public Long getInDomainId() { return inDomainId; } + @Override + public ProjectAccount.Role getAccountRole() { + return accountRole; + } + + 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; @@ -146,6 +174,15 @@ public long getAccountId() { return forAccountId == null ? -1 : forAccountId; } + @Override + public Long getForUserId() { + return forUserId == null ? -1 : forUserId; + } + + public void setForUserId(Long forUserId) { + this.forUserId = forUserId; + } + @Override public Class getEntityType() { return ProjectInvitation.class; 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..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 @@ -25,10 +25,16 @@ 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 canUserAccessProjectAccount(long accountId, long userId, long projectAccountId); + boolean canAccessProjectAccount(long accountId, long projectAccountId); boolean canModifyProjectAccount(long accountId, long projectAccountId); @@ -40,4 +46,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..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 @@ -18,7 +18,6 @@ import java.util.List; - import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -34,9 +33,11 @@ @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()); protected ProjectAccountDaoImpl() { @@ -45,18 +46,27 @@ 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(); + 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()); @@ -74,6 +84,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(); @@ -84,13 +102,36 @@ public List listByProjectId(long projectId) { @Override public ProjectAccountVO findByProjectIdAccountId(long projectId, long accountId) { + SearchCriteria sc = ProjectAccountSearch.create(); + sc.setParameters("projectId", projectId); + sc.setParameters("accountId", 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 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(); @@ -120,7 +161,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); } @@ -152,4 +193,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/projects/dao/ProjectInvitationDao.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDao.java index 3788641f38ae..976d53998e2e 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDao.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDao.java @@ -25,6 +25,8 @@ public interface ProjectInvitationDao extends GenericDao { ProjectInvitationVO findByAccountIdProjectId(long accountId, long projectId, State... inviteState); + ProjectInvitationVO findByUserIdProjectId(long userId, long accountId, long projectId, State... inviteState); + List listExpiredInvitations(); boolean expirePendingInvitations(long timeOut); 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 2fc74b9ee85f..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 @@ -19,7 +19,6 @@ import java.sql.Date; import java.util.List; - import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -35,11 +34,13 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase AllFieldsSearch; protected final SearchBuilder InactiveSearch; + protected final SearchBuilder ProjectAccountInviteSearch; protected ProjectInvitationDaoImpl() { AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getForAccountId(), SearchCriteria.Op.EQ); AllFieldsSearch.and("projectId", AllFieldsSearch.entity().getProjectId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("userId", AllFieldsSearch.entity().getForUserId(), SearchCriteria.Op.EQ); AllFieldsSearch.and("created", AllFieldsSearch.entity().getCreated(), SearchCriteria.Op.EQ); AllFieldsSearch.and("projectAccountId", AllFieldsSearch.entity().getState(), SearchCriteria.Op.EQ); AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), SearchCriteria.Op.IN); @@ -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) { @@ -69,6 +76,21 @@ public ProjectInvitationVO findByAccountIdProjectId(long accountId, long project return findOneBy(sc); } + @Override + public ProjectInvitationVO findByUserIdProjectId(long userId, long accountId, long projectId, State... inviteState) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("userId", userId); + sc.setParameters("accountId", accountId); + if (projectId != -1) { + sc.setParameters("projectId", projectId); + } + if (inviteState != null && inviteState.length > 0) { + sc.setParameters("state", (Object[])inviteState); + } + + return findOneBy(sc); + } + @Override public List listExpiredInvitations() { SearchCriteria sc = AllFieldsSearch.create(); 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/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..c700f8478bb9 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java @@ -0,0 +1,72 @@ +// 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..b15382612f48 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRoleDaoImpl.java @@ -0,0 +1,61 @@ +// 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; +import com.google.common.base.Strings; + +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(); + if (!Strings.isNullOrEmpty(name)) { + 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..dd4b035021be --- /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.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; + +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..1eae8f3fa0ea --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ProjectRolePermissionsDaoImpl.java @@ -0,0 +1,147 @@ +// 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.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; + +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 static final Logger LOGGER = Logger.getLogger(ProjectRolePermissionsDaoImpl.class); + 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().getProjectRoleId(), 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("projectRoleId", 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); + return update(rolePermission.getId(), projectRolePermissionVO); + } + + @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 dec12512311f..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 @@ -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.Role; import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.RolePermission.Permission; +import org.apache.cloudstack.acl.RolePermissionEntity; import org.apache.cloudstack.acl.RolePermissionVO; -import java.util.List; +import com.cloud.utils.db.GenericDao; public interface RolePermissionsDao extends GenericDao { /** @@ -48,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 5b4f7a7582f1..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 @@ -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.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; + 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.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index 6ec5dcd5b818..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 @@ -19,6 +19,156 @@ -- Schema upgrade from 4.14.0.0 to 4.15.0.0 --; +-- 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`, `project_id`), + 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`) 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 +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 incorporate 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, + project_invitations.project_role_id, + 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, + user.username user_name, + 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, + 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`.`project_role` ON project_account.project_role_id = project_role.id + left join + `cloud`.`user` ON (project_account.user_id = user.id); + +ALTER 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/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java index 8786bfdd2286..600b25782f14 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java @@ -905,7 +905,7 @@ private void peerScan() throws ActiveFencingException { try { JmxUtil.registerMBean("ClusterManager", "Node " + mshost.getId(), new ClusterManagerMBeanImpl(this, mshost)); } catch (final Exception e) { - s_logger.warn("Unable to regiester cluster node into JMX monitoring due to exception " + ExceptionUtil.toString(e)); + s_logger.warn("Unable to register cluster node into JMX monitoring due to exception " + ExceptionUtil.toString(e)); } } } @@ -985,7 +985,7 @@ public ManagementServerHostVO doInTransaction(final TransactionStatus status) { _mshostPeerDao.clearPeerInfo(_mshostId); - // use seperate thread for heartbeat updates + // use separate thread for heartbeat updates _heartbeatScheduler.scheduleAtFixedRate(getHeartbeatTask(), HeartbeatInterval.value(), HeartbeatInterval.value(), TimeUnit.MILLISECONDS); _notificationExecutor.submit(getNotificationTask()); 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..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 @@ -25,10 +25,12 @@ 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 org.apache.cloudstack.acl.RolePermissionEntity.Permission; 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 +46,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 +88,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..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 @@ -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,16 @@ 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 org.apache.cloudstack.acl.RolePermissionEntity.Permission; + +import junit.framework.TestCase; @RunWith(MockitoJUnitRunner.class) public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase { @@ -117,7 +121,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 +129,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 +137,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 +149,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..5a17bb993eb3 --- /dev/null +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -0,0 +1,148 @@ +// 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.acl.RolePermissionEntity.Permission; + +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; + } + + if (accountService.isRootAdmin(userAccount.getId()) || accountService.isDomainAdmin(userAccount.getAccountId())) { + 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 + 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) { + 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); + } + } + } + + return true; + } + + @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/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/hypervisors/baremetal/pom.xml b/plugins/hypervisors/baremetal/pom.xml index 0889bafed9f5..aecb9cca3f4f 100755 --- a/plugins/hypervisors/baremetal/pom.xml +++ b/plugins/hypervisors/baremetal/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.15.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/pom.xml b/plugins/pom.xml index 18d48db4928c..051cd4e480f2 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -45,6 +45,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/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/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 5184d660e9b8..1077d2bc4803 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -21,9 +21,15 @@ 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; import com.cloud.dc.DataCenter; @@ -32,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; @@ -40,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; @@ -64,7 +74,7 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { @Inject ProjectManager _projectMgr; @Inject - ProjectAccountDao _projecAccountDao; + ProjectAccountDao _projectAccountDao; @Inject NetworkModel _networkMgr; @Inject @@ -79,7 +89,14 @@ 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() { super(); } @@ -107,6 +124,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); } @@ -115,7 +133,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 @@ -154,7 +171,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) { @@ -164,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); @@ -171,10 +188,59 @@ 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 given command '" + apiCommandName + "' either does not exist or is not available for the 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("The given command '" + apiCommandName + "' either does not exist or is not available for the user"); + } + } + } + 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/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 8d476ba78dd7..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); } @@ -1833,6 +1837,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 bc5cbcb99b9c..dbbc9848bbab 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 a0a71d099b76..586d80b4745e 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,50 @@ 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.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 +212,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 +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); - 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 c42980bf6951..f1a516fecdeb 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()) { @@ -305,13 +310,14 @@ 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) + ")"); // 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 { @@ -347,6 +353,41 @@ 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]; + CallContext.current().setApiName(commandName); + 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/dispatch/ParamProcessWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java index b1051a72fdd7..d4299861e71e 100644 --- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java @@ -269,20 +269,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)).toArray(Account[]::new); + } 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/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 558e0aeec5c7..f13b7953e336 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -196,6 +196,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; @@ -223,7 +224,9 @@ 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.user.dao.UserDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; @@ -410,6 +413,11 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private RouterHealthCheckResultDao routerHealthCheckResultDao; + @Inject + private ProjectInvitationDao projectInvitationDao; + + @Inject + private UserDao userDao; /* * (non-Javadoc) * @@ -1386,6 +1394,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(); @@ -1394,8 +1403,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); @@ -1419,11 +1431,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())) { @@ -1434,11 +1458,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(); @@ -1452,6 +1482,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) { @@ -1474,6 +1512,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); } @@ -1525,10 +1567,12 @@ 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(); Account caller = CallContext.current().getCallingAccount(); + User callingUser = CallContext.current().getCallingUser(); List permittedAccounts = new ArrayList(); Ternary domainIdRecursiveListProject = new Ternary(domainId, isRecursive, null); @@ -1540,7 +1584,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); @@ -1553,6 +1597,12 @@ public Pair, Integer> listProjectInvitationsIntern sc.setParameters("projectId", projectId); } + if (invitation != null) { + sc.setParameters("userId", invitation.getForUserId()); + } else if (userId != null) { + sc.setParameters("userId", userId); + } + if (state != null) { sc.setParameters("state", state); } @@ -1566,7 +1616,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()); + } @@ -1582,14 +1636,15 @@ 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(); - + Long projectRoleId = cmd.getProjectRoleId(); // 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); @@ -1599,7 +1654,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); } @@ -1612,6 +1668,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); @@ -1624,6 +1683,14 @@ public Pair, Integer> listProjectAccountsInternal(Lis sc.setParameters("accountName", accountName); } + if (projectRoleId != null) { + sc.setParameters("projectRoleId", projectRoleId); + } + + 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 4baf25cfca3a..88a7639ac914 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; @@ -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..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 @@ -52,9 +52,11 @@ 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()); + 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/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/dao/ProjectJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java index bedbaaa667ee..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,10 @@ 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; import javax.inject.Inject; @@ -35,9 +38,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 +60,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; @@ -82,7 +94,22 @@ 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 -> 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 4766de4faedb..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 @@ -72,6 +72,21 @@ public class ProjectAccountJoinVO extends BaseViewVO implements InternalIdentity @Column(name = "project_name") private String projectName; + @Column(name = "user_id") + private Long userId; + + @Column(name = "user_name") + private String username; + + @Column(name = "project_role_id") + private Long projectRoleId; + + @Column(name = "project_role_uuid") + private String projectRoleUuid; + + @Column(name = "user_uuid") + private String userUuid; + public ProjectAccountJoinVO() { } @@ -127,4 +142,21 @@ public String getProjectUuid() { public String getProjectName() { return projectName; } + + public Long getUserId() { return userId; } + + public Long getProjectRoleId() { + return projectRoleId; + } + + public String getProjectRoleUuid() { + return projectRoleUuid; + } + + public String getUserUuid() { return userUuid; } + + public String getUsername() { + return username; + } + } 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/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; + } } diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index aeba07cc46ea..aabcf2b10bfc 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 fb3626bfb6a1..90a27fcafd01 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; @@ -27,6 +28,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 +40,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 +78,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 +127,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 +180,38 @@ 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; + } + + 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 - 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 +225,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 +238,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 +258,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); + 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()); @@ -241,6 +286,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 +307,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); @@ -277,11 +324,11 @@ public Boolean doInTransaction(TransactionStatus status) { project.setState(State.Disabled); boolean updateResult = _projectDao.update(project.getId(), project); //owner can be already removed at this point, so adding the conditional check - Account projectOwner = getProjectOwner(project.getId()); - if (projectOwner != null) { - _resourceLimitMgr.decrementResourceCount(projectOwner.getId(), ResourceType.project); + List projectOwners = getProjectOwners(project.getId()); + if (projectOwners != null) { + for (Long projectOwner : projectOwners) + _resourceLimitMgr.decrementResourceCount(projectOwner, ResourceType.project); } - return updateResult; } }); @@ -323,7 +370,6 @@ public Boolean doInTransaction(TransactionStatus status) { s_logger.debug("Removing all invitations for the project " + project + " as a part of project cleanup..."); _projectInvitationDao.cleanupInvitations(project.getId()); - return result; } }); @@ -364,8 +410,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 +455,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()).getId()).collect(Collectors.toList()); + } + return null; + } + @Override public ProjectVO findByProjectAccountId(long projectAccountId) { return _projectDao.findByProjectAccountId(projectAccountId); @@ -414,6 +474,74 @@ 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, String username, String email, 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.getUserByName(username, project.getDomainId()); + if (user == null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Invalid user ID provided"); + ex.addProxyObject(String.valueOf(username), "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.getProjectId().equals(projectId)) { + throw new InvalidParameterValueException("Invalid project role ID for the given project"); + } + } + + if (_invitationRequired) { + 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"); + } + if (assignUserToProject(project, user.getId(), 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); + return false; + } + } + @Override public Project findByNameAndDomainId(String name, long domainId) { return _projectDao.findByNameAndDomain(name, domainId); @@ -429,7 +557,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); } @@ -443,9 +576,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.getAccountId(), 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); + _projectAccountDao.update(futureOwner.getId(), futureOwner); + 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) @@ -465,47 +618,47 @@ public Project updateProject(final long projectId, final String displayText, fin Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { - if (displayText != null) { - project.setDisplayText(displayText); - _projectDao.update(projectId, project); - } + 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) { + 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"); - } + " 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); + //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); + //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); + //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); - } - } + } else { + s_logger.trace("Future owner " + newOwnerName + "is already the owner of the project id=" + projectId); + } + } } }); @@ -513,9 +666,76 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Resour } + @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, Long userId, + Role newRole) 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); + } + + 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 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 (isTheOnlyProjectOwner(projectId, newProjectAcc, caller) && newRole != Role.Admin) { + throw new InvalidParameterValueException("Cannot demote the only admin of the project"); + } + updateProjectAccount(newProjectAcc, newRole, updatedAcc.getId()); + } else if (userId != null) { + User user = validateUser(userId, project.getDomainId()); + if (user == null) { + 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) { + throw new InvalidParameterValueException("User " + userId + + " doesn't belong to the project. Add it to the project first and then change the project's ownership"); + } + + 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"); + } + + updateProjectAccount(newProjectUser, newRole, user.getAccountId()); + + } + } + }); + 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 +770,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 +782,26 @@ 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.getProjectId() != projectId) { + throw new InvalidParameterValueException("Invalid project role ID for the given project"); + } + } + if (_invitationRequired) { - return inviteAccountToProject(project, account, email); + return inviteAccountToProject(project, account, email, projectRoleType, projectRole); } 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, + Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) { return true; } else { s_logger.warn("Failed to add account " + accountName + " to project id=" + projectId); @@ -576,9 +810,10 @@ public boolean addAccountToProject(long projectId, String accountName, String em } } - private boolean inviteAccountToProject(Project project, Account account, String email) { + private boolean inviteAccountToProject(Project project, Account account, String email, Role role,ProjectRole projectRole) { if (account != null) { - if (createAccountInvitation(project, account.getId()) != 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); @@ -589,7 +824,8 @@ private boolean inviteAccountToProject(Project project, Account account, String if (email != null) { //generate the token String token = generateToken(10); - if (generateTokenBasedInvitation(project, email, token) != 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); @@ -600,6 +836,37 @@ private boolean inviteAccountToProject(Project project, Account account, String return false; } + private boolean inviteUserToProject(Project project, User user, String email, Role role, ProjectRole projectRole) { + if (email == 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); + return false; + } + } else { + //generate the token + String token = generateToken(10); + 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); + return false; + } + } + } + + 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 )) { + return true; + } + return false; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_REMOVE, eventDescription = "removing account from project", async = true) public boolean deleteAccountFromProject(long projectId, String accountName) { @@ -628,11 +895,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,7 +909,7 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { } //can't remove the owner of the project - if (projectAccount.getAccountRole() == Role.Admin) { + 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"); @@ -652,18 +920,104 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { return deleteAccountFromProject(projectId, account.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); + @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; } - ProjectInvitation invitation = _projectInvitationDao.persist(new ProjectInvitationVO(project.getId(), accountId, project.getDomainId(), null, null)); + 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) { + deletePendingInvite(projectId, user); + 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); + } - return invitation; + private void deletePendingInvite(Long projectId, User user) { + ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId); + if (invite != null) { + boolean success = _projectInvitationDao.remove(invite.getId()); + if (success){ + s_logger.info("Successfully deleted invite pending for the user : "+user.getUsername()); + } else { + s_logger.info("Failed to delete project invite for user: "+ user.getUsername()); + } + } } @DB - public boolean activeInviteExists(final Project project, final Long accountId, final String email) { + private boolean deleteUserFromProject(Long projectId, User user) { + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + boolean success = true; + ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId()); + success = _projectAccountDao.remove(projectAccount.getId()); + + if (success) { + s_logger.debug("Removed user " + user.getId() + " from project. Removing any invite sent to the user"); + ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId); + if (invite != null) { + success = success && _projectInvitationDao.remove(invite.getId()); + } + } + return success; + } + }); + } + + 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); + } + + ProjectInvitationVO invitationVO = new ProjectInvitationVO(project.getId(), accountId, project.getDomainId(), null, null); + if (userId != null) { + invitationVO.setForUserId(userId); + } + if (role != null) { + invitationVO.setAccountRole(role); + } + if (projectRoleId != null) { + invitationVO.setProjectRoleId(projectRoleId); + } + return _projectInvitationDao.persist(invitationVO); + } + + @DB + public boolean activeInviteExists(final Project project, final Long accountId, Long userId, final String email) { return Transaction.execute(new TransactionCallback() { @Override public Boolean doInTransaction(TransactionStatus status) { @@ -671,6 +1025,8 @@ public Boolean doInTransaction(TransactionStatus status) { ProjectInvitationVO invite = null; if (accountId != null) { invite = _projectInvitationDao.findByAccountIdProjectId(accountId, project.getId()); + } else if (userId != null) { + invite = _projectInvitationDao.findByUserIdProjectId(userId, accountId, project.getId()); } else if (email != null) { invite = _projectInvitationDao.findByEmailAndProjectId(email, project.getId()); } @@ -686,6 +1042,8 @@ public Boolean doInTransaction(TransactionStatus status) { //remove the expired/declined invitation if (accountId != null) { s_logger.debug("Removing invitation in state " + invite.getState() + " for account id=" + accountId + " to project " + project); + } else if (userId != null) { + s_logger.debug("Removing invitation in state " + invite.getState() + " for user id=" + userId + " to project " + project); } else if (email != null) { s_logger.debug("Removing invitation in state " + invite.getState() + " for email " + email + " to project " + project); } @@ -699,13 +1057,24 @@ public Boolean doInTransaction(TransactionStatus status) { }); } - public ProjectInvitation generateTokenBasedInvitation(Project project, String email, String token) { + 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, email)) { + if (activeInviteExists(project, null, null, email)) { throw new InvalidParameterValueException("There is already a pending invitation for email " + email + " to the project id=" + project); } - ProjectInvitation projectInvitation = _projectInvitationDao.persist(new ProjectInvitationVO(project.getId(), null, project.getDomainId(), email, token)); + ProjectInvitationVO projectInvitationVO = new ProjectInvitationVO(project.getId(), null, project.getDomainId(), email, token); + if (userId != null) { + projectInvitationVO.setForUserId(userId); + } + if (role != null) { + projectInvitationVO.setAccountRole(role); + } + if (projectRoleId != null) { + projectInvitationVO.setProjectRoleId(projectRoleId); + } + + ProjectInvitation projectInvitation = _projectInvitationDao.persist(projectInvitationVO); try { _emailInvite.sendInvite(token, email, project.getId()); } catch (Exception ex) { @@ -726,23 +1095,19 @@ private boolean expireInvitation(ProjectInvitationVO invite) { @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_INVITATION_UPDATE, eventDescription = "updating project invitation", async = true) - public boolean updateInvitation(final long projectId, String accountName, String token, final boolean accept) { + public boolean updateInvitation(final long projectId, String accountName, Long userId, String token, final boolean accept) { Account caller = CallContext.current().getCallingAccount(); Long accountId = null; + User user = null; boolean result = true; - //if accountname and token are null, default accountname to caller's account name - if (accountName == null && token == null) { - accountName = caller.getAccountName(); - } - //check that the project exists final Project project = getProject(projectId); 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 Account account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); @@ -752,16 +1117,30 @@ public boolean updateInvitation(final long projectId, String accountName, String //verify permissions _accountMgr.checkAccess(caller, null, true, account); - accountId = account.getId(); + } 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) { - invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); + if (accountName != null) { + invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); + } else { + invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId, ProjectInvitation.State.Pending); + } } else { invite = _projectInvitationDao.findPendingByTokenAndProjectId(token, projectId, ProjectInvitation.State.Pending); } @@ -771,10 +1150,11 @@ public boolean updateInvitation(final long projectId, String accountName, String 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; + final User finalUser = user; + ProjectInvitationVO finalInvite = invite; result = Transaction.execute(new TransactionCallback() { @Override public Boolean doInTransaction(TransactionStatus status) { @@ -783,25 +1163,32 @@ public Boolean doInTransaction(TransactionStatus status) { ProjectInvitation.State newState = accept ? ProjectInvitation.State.Completed : ProjectInvitation.State.Declined; //update invitation - s_logger.debug("Marking invitation " + inviteFinal + " with state " + newState); - inviteFinal.setState(newState); - result = _projectInvitationDao.update(inviteFinal.getId(), inviteFinal); + s_logger.debug("Marking invitation " + inviteFinal + " with state " + newState); + inviteFinal.setState(newState); + result = _projectInvitationDao.update(inviteFinal.getId(), inviteFinal); if (result && accept) { //check if account already exists for the project (was added before invitation got accepted) - ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountIdFinal); - if (projectAccount != null) { - s_logger.debug("Account " + accountNameFinal + " already added to the project id=" + projectId); + 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, finalInvite.getProjectRoleId()); + } } else { - assignAccountToProject(project, accountIdFinal, ProjectAccount.Role.Regular); + ProjectAccount projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, finalUser.getAccountId(), finalUser.getId()); + if (projectAccount != null) { + s_logger.debug("User " + finalUser.getId() + "has already been added to the project id=" + projectId); + } else { + assignUserToProject(project, finalInvite.getForUserId(), finalUser.getAccountId(), finalInvite.getAccountRole(), finalInvite.getProjectRoleId()); + } } } else { - s_logger.warn("Failed to update project invitation " + inviteFinal + " with state " + newState); + s_logger.warn("Failed to update project invitation " + inviteFinal + " with state " + newState); } - - return result; - } - }); + return result; + }}); } } else { throw new InvalidParameterValueException("Unable to find invitation for account name=" + accountName + " to the project id=" + projectId); @@ -830,6 +1217,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 +1259,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)) { @@ -911,7 +1300,6 @@ public static String generateToken(int length) { } return sb.toString(); } - class EmailInvite { private Session _smtpSession; private final String _smtpHost; @@ -1012,6 +1400,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 926aca94f7ea..7a32578e1698 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()); @@ -2807,7 +2809,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 9fb185833caf..a20090cf7c14 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; @@ -521,6 +520,7 @@ 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"); } + return; } @@ -2034,7 +2034,7 @@ public Account finalizeOwner(Account caller, String accountName, Long domainId, return caller; } } else { - if ((accountName == null && domainId != null) || (accountName != null && domainId == null)) { + if (accountName != null && domainId == null) { throw new InvalidParameterValueException("AccountName and domainId must be specified together"); } // regular user can't create/list resources for other people 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..90d8e1ecec2c --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/acl/ProjectRoleManagerImpl.java @@ -0,0 +1,315 @@ +// 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.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; +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.AccountService; +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; + @Inject + AccountService accountService; + + 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"); + } + + 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()); + if (projectAccount == null) { + throw new PermissionDeniedException("User/Account not part of project"); + } + } + 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; + } + 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) { + 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."); + } + } + }); + } + + @Override + @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); + + } + + @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 0546cc85a0a3..dce518c135e1 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; @@ -75,7 +76,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"); } @@ -85,7 +86,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"); } } @@ -305,7 +306,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(); if (role.isDefault()) { throw new PermissionDeniedException("Role permission cannot be added for Default roles"); @@ -334,7 +335,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(); if (role.isDefault()) { throw new PermissionDeniedException("Role permission cannot be updated for Default roles"); @@ -438,7 +439,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; } 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..f8af30f2eb2f 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 @@ -80,12 +86,17 @@ public Project findByNameAndDomainId(String name, long domainId) { @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, 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; } @@ -97,7 +108,13 @@ public boolean deleteAccountFromProject(long projectId, String accountName) { } @Override - public boolean updateInvitation(long projectId, String accountName, String token, boolean accept) { + public boolean deleteUserFromProject(long projectId, long userId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean updateInvitation(long projectId, String accountName, Long userId, String token, boolean accept) { // TODO Auto-generated method stub return false; } @@ -203,4 +220,9 @@ public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) { return null; } + @Override + public boolean addUserToProject(Long projectId, String username, String email, 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..e37507b98e48 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,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); - accountManagerImpl.retrieveAndValidateAccount(userVoMock); } 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..16cce8c37cd8 --- /dev/null +++ b/test/integration/smoke/test_enable_role_based_users_in_projects.py @@ -0,0 +1,271 @@ +# 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", + }, + "useracc": { + "email": "user1@test.com", + "firstname": "User", + "lastname": "User", + "username": "user1", + "password": "fr3sca", + }, + "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 = [self.project] + + 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 + + @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, + username=self.useraccount.user[0].username, + 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 + + + @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) + + self.useraccount1 = Account.create( + self.apiclient, + 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, role='Admin') + self.assertEqual(len(project_accounts), 2, "account not added with admin Role") + + 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.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 + diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 231f31a91dc4..10ea666b2de9 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -3997,7 +3997,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() @@ -4007,7 +4007,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): @@ -4039,7 +4042,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() @@ -4048,6 +4051,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): @@ -4058,6 +4065,29 @@ def deleteAccount(self, apiclient, account): cmd.account = account return apiclient.deleteAccountFromProject(cmd) + def addUser(self, apiclient, username=None, email=None, projectroleid=None, roletype=None): + """Add user to project""" + + cmd = addUserToProject.addUserToProjectCmd() + cmd.projectid = self.id + if username: + cmd.username = username + 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.""" @@ -4086,7 +4116,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() @@ -4094,6 +4124,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 @@ -5421,3 +5453,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))