server: Dynamic roles improvements. Add-on functionality below. (#4071)

- Create a role from any of the existing role, using new parameter roleid in createRole API
- Import a role with its rules, using a new importRole API
- New default roles for Read-Only and Support Admin & User
- No modifications allowed for Default roles
- Cleaned up old NetApp APIs from role_permissions table.
This commit is contained in:
sureshanaparti 2020-07-07 10:56:43 +05:30 committed by GitHub
parent a73712ec4e
commit 5040283db9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1119 additions and 64 deletions

View File

@ -185,6 +185,7 @@ public class EventTypes {
public static final String EVENT_ROLE_CREATE = "ROLE.CREATE";
public static final String EVENT_ROLE_UPDATE = "ROLE.UPDATE";
public static final String EVENT_ROLE_DELETE = "ROLE.DELETE";
public static final String EVENT_ROLE_IMPORT = "ROLE.IMPORT";
public static final String EVENT_ROLE_PERMISSION_CREATE = "ROLE.PERMISSION.CREATE";
public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE";
public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE";
@ -691,6 +692,7 @@ public class EventTypes {
entityEventDetails.put(EVENT_ROLE_CREATE, Role.class);
entityEventDetails.put(EVENT_ROLE_UPDATE, Role.class);
entityEventDetails.put(EVENT_ROLE_DELETE, Role.class);
entityEventDetails.put(EVENT_ROLE_IMPORT, Role.class);
entityEventDetails.put(EVENT_ROLE_PERMISSION_CREATE, RolePermission.class);
entityEventDetails.put(EVENT_ROLE_PERMISSION_UPDATE, RolePermission.class);
entityEventDetails.put(EVENT_ROLE_PERMISSION_DELETE, RolePermission.class);

View File

@ -24,4 +24,5 @@ public interface Role extends InternalIdentity, Identity {
String getName();
RoleType getRoleType();
String getDescription();
boolean isDefault();
}

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.acl;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.acl.RolePermission.Permission;
import org.apache.cloudstack.framework.config.ConfigKey;
@ -39,13 +40,17 @@ public interface RoleService {
Role createRole(String name, RoleType roleType, String description);
Role createRole(String name, Role role, String description);
Role importRole(String name, RoleType roleType, String description, List<Map<String, Object>> rules, boolean forced);
Role updateRole(Role role, String name, RoleType roleType, String description);
boolean deleteRole(Role role);
RolePermission findRolePermission(Long id);
RolePermission findRolePermissionByUuid(String uuid);
RolePermission findRolePermissionByRoleIdAndRule(Long roleId, String rule);
RolePermission createRolePermission(Role role, Rule rule, Permission permission, String description);
@ -77,4 +82,6 @@ public interface RoleService {
List<Role> findRolesByType(RoleType roleType);
List<RolePermission> findAllPermissionsBy(Long roleId);
Permission getRolePermission(String permission);
}

View File

@ -478,6 +478,7 @@ public class ApiConstants {
public static final String ROLE_NAME = "rolename";
public static final String PERMISSION = "permission";
public static final String RULE = "rule";
public static final String RULES = "rules";
public static final String RULE_ID = "ruleid";
public static final String RULE_ORDER = "ruleorder";
public static final String USER = "user";

View File

@ -43,4 +43,5 @@ public interface ApiServerService {
public Class<?> getCmdClass(String cmdName);
public boolean isValidApiName(String apiName);
}

View File

@ -45,13 +45,9 @@ public class CreateRoleCmd extends RoleCmd {
description = "creates a role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty})
private String roleName;
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true,
description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User",
validations = {ApiArgValidator.NotNullOrEmpty})
private String roleType;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role")
private String roleDescription;
@Parameter(name = ApiConstants.ROLE_ID, type = CommandType.UUID, entityType = RoleResponse.class,
description = "ID of the role to be cloned from. Either roleid or type must be passed in")
private Long roleId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
@ -61,12 +57,8 @@ public class CreateRoleCmd extends RoleCmd {
return roleName;
}
public RoleType getRoleType() {
return RoleType.fromString(roleType);
}
public String getRoleDescription() {
return roleDescription;
public Long getRoleId() {
return roleId;
}
/////////////////////////////////////////////////////
@ -85,11 +77,39 @@ public class CreateRoleCmd extends RoleCmd {
@Override
public void execute() {
CallContext.current().setEventDetails("Role: " + getRoleName() + ", type:" + getRoleType() + ", description: " + getRoleDescription());
final Role role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription());
validateRoleParameters();
Role role = null;
if (getRoleId() != null) {
Role existingRole = roleService.findRole(getRoleId());
if (existingRole == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided");
}
CallContext.current().setEventDetails("Role: " + getRoleName() + ", from role: " + getRoleId() + ", description: " + getRoleDescription());
role = roleService.createRole(getRoleName(), existingRole, getRoleDescription());
} else {
CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription());
role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription());
}
if (role == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create role");
}
setupResponse(role);
}
private void validateRoleParameters() {
if (getRoleType() == null && getRoleId() == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Neither role type nor role ID is provided");
}
if (getRoleType() != null && getRoleId() != null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Both role type and role ID should not be specified");
}
if (getRoleId() != null && getRoleId() < 1L) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided");
}
}
}

View File

@ -0,0 +1,149 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.acl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.RoleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.collections.MapUtils;
import com.cloud.user.Account;
import com.google.common.base.Strings;
@APICommand(name = ImportRoleCmd.APINAME, description = "Imports a role based on provided map of rule permissions", responseObject = RoleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.15.0",
authorized = {RoleType.Admin})
public class ImportRoleCmd extends RoleCmd {
public static final String APINAME = "importRole";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,
description = "Creates a role with this unique name", validations = {ApiArgValidator.NotNullOrEmpty})
private String roleName;
@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true,
description = "Rules param list, rule and permission is must. Example: rules[0].rule=create*&rules[0].permission=allow&rules[0].description=create%20rule&rules[1].rule=list*&rules[1].permission=allow&rules[1].description=listing")
private Map rules;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN,
description = "Force create a role with the same name. This overrides the role type, description and rule permissions for the existing role. Default is false.")
private Boolean forced;
@Inject
ApiServerService _apiServer;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getRoleName() {
return roleName;
}
// Returns list of rule maps. Each map corresponds to a rule with the details in the keys: rule, permission & description
public List<Map<String, Object>> getRules() {
if (MapUtils.isEmpty(rules)) {
return null;
}
List<Map<String, Object>> rulesDetails = new ArrayList<>();
Collection rulesCollection = rules.values();
Iterator iter = rulesCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> detail = (HashMap<String, String>)iter.next();
Map<String, Object> ruleDetails = new HashMap<>();
String rule = detail.get(ApiConstants.RULE);
if (Strings.isNullOrEmpty(rule)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Empty rule provided in rules param");
}
if (!rule.contains("*") && !_apiServer.isValidApiName(rule)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid api name: " + rule + " provided in rules param");
}
ruleDetails.put(ApiConstants.RULE, new Rule(rule));
String permission = detail.get(ApiConstants.PERMISSION);
if (Strings.isNullOrEmpty(permission)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid permission: "+ permission + " provided in rules param");
}
ruleDetails.put(ApiConstants.PERMISSION, roleService.getRolePermission(permission));
String description = detail.get(ApiConstants.DESCRIPTION);
if (!Strings.isNullOrEmpty(permission)) {
ruleDetails.put(ApiConstants.DESCRIPTION, description);
}
rulesDetails.add(ruleDetails);
}
return rulesDetails;
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() {
if (getRoleType() == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided");
}
CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription());
Role role = roleService.importRole(getRoleName(), getRoleType(), getRoleDescription(), getRules(), isForced());
if (role == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to import role");
}
setupResponse(role);
}
}

View File

@ -97,6 +97,7 @@ public class ListRolesCmd extends BaseCmd {
roleResponse.setRoleName(role.getName());
roleResponse.setRoleType(role.getRoleType());
roleResponse.setDescription(role.getDescription());
roleResponse.setIsDefault(role.isDefault());
roleResponse.setObjectName("role");
roleResponses.add(roleResponse);
}

View File

@ -18,11 +18,41 @@
package org.apache.cloudstack.api.command.admin.acl;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.RoleResponse;
import com.google.common.base.Strings;
public abstract class RoleCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User")
private String roleType;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role")
private String roleDescription;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public RoleType getRoleType() {
if (!Strings.isNullOrEmpty(roleType)) {
return RoleType.fromString(roleType);
}
return null;
}
public String getRoleDescription() {
return roleDescription;
}
protected void setupResponse(final Role role) {
final RoleResponse response = new RoleResponse();
response.setId(role.getUuid());

View File

@ -18,7 +18,6 @@
package org.apache.cloudstack.api.command.admin.acl;
import com.cloud.user.Account;
import com.google.common.base.Strings;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
@ -49,9 +48,6 @@ public class UpdateRoleCmd extends RoleCmd {
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, description = "creates a role with this unique name")
private String roleName;
@Parameter(name = ApiConstants.TYPE, type = BaseCmd.CommandType.STRING, description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User")
private String roleType;
@Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "The description of the role")
private String roleDescription;
@ -67,17 +63,6 @@ public class UpdateRoleCmd extends RoleCmd {
return roleName;
}
public RoleType getRoleType() {
if (!Strings.isNullOrEmpty(roleType)) {
return RoleType.fromString(roleType);
}
return null;
}
public String getRoleDescription() {
return roleDescription;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -43,6 +43,10 @@ public class RoleResponse extends BaseResponse {
@Param(description = "the description of the role")
private String roleDescription;
@SerializedName(ApiConstants.IS_DEFAULT)
@Param(description = "true if role is default, false otherwise")
private Boolean isDefault;
public void setId(String id) {
this.id = id;
}
@ -60,4 +64,8 @@ public class RoleResponse extends BaseResponse {
public void setDescription(String description) {
this.roleDescription = description;
}
public void setIsDefault(Boolean isDefault) {
this.isDefault = isDefault;
}
}

View File

@ -0,0 +1,102 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.test;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd;
import org.apache.cloudstack.api.response.RoleResponse;
import org.apache.cloudstack.api.ServerApiException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import static org.mockito.Mockito.when;
public class CreateRoleCmdTest {
private CreateRoleCmd createRoleCmd;
private RoleService roleService;
private Role role;
@Before
public void setUp() {
roleService = Mockito.spy(RoleService.class);
createRoleCmd = new CreateRoleCmd();
ReflectionTestUtils.setField(createRoleCmd,"roleService",roleService);
ReflectionTestUtils.setField(createRoleCmd,"roleName","testuser");
ReflectionTestUtils.setField(createRoleCmd,"roleDescription","User test");
role = Mockito.mock(Role.class);
}
@Test
public void testCreateRoleWithRoleType() {
ReflectionTestUtils.setField(createRoleCmd,"roleType", "User");
when(role.getId()).thenReturn(1L);
when(role.getUuid()).thenReturn("12345-abcgdkajd");
when(role.getDescription()).thenReturn("User test");
when(role.getName()).thenReturn("testuser");
when(role.getRoleType()).thenReturn(RoleType.User);
when(roleService.createRole(createRoleCmd.getRoleName(), createRoleCmd.getRoleType(), createRoleCmd.getRoleDescription())).thenReturn(role);
createRoleCmd.execute();
RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject();
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), role.getName());
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), role.getDescription());
}
@Test
public void testCreateRoleWithExistingRole() {
ReflectionTestUtils.setField(createRoleCmd,"roleId",1L);
when(roleService.findRole(createRoleCmd.getRoleId())).thenReturn(role);
Role newRole = Mockito.mock(Role.class);
when(newRole.getId()).thenReturn(2L);
when(newRole.getUuid()).thenReturn("67890-xyztestid");
when(newRole.getDescription()).thenReturn("User test");
when(newRole.getName()).thenReturn("testuser");
when(newRole.getRoleType()).thenReturn(RoleType.User);
when(roleService.createRole(createRoleCmd.getRoleName(), role, createRoleCmd.getRoleDescription())).thenReturn(newRole);
createRoleCmd.execute();
RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject();
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), newRole.getName());
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), newRole.getDescription());
}
@Test(expected = ServerApiException.class)
public void testCreateRoleWithNonExistingRole() {
ReflectionTestUtils.setField(createRoleCmd,"roleId",1L);
when(roleService.findRole(createRoleCmd.getRoleId())).thenReturn(null);
createRoleCmd.execute();
Assert.fail("An exception should have been thrown: " + ServerApiException.class);
}
@Test(expected = ServerApiException.class)
public void testCreateRoleValidateNeitherRoleIdNorTypeParameters() {
createRoleCmd.execute();
Assert.fail("An exception should have been thrown: " + ServerApiException.class);
}
@Test(expected = ServerApiException.class)
public void testCreateRoleValidateBothRoleIdAndTypeParameters() {
ReflectionTestUtils.setField(createRoleCmd,"roleId",1L);
ReflectionTestUtils.setField(createRoleCmd,"roleType", "User");
createRoleCmd.execute();
Assert.fail("An exception should have been thrown: " + ServerApiException.class);
}
}

View File

@ -0,0 +1,131 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.test;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.acl.ImportRoleCmd;
import org.apache.cloudstack.api.response.RoleResponse;
import org.apache.cloudstack.api.ServerApiException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.cloud.exception.InvalidParameterValueException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
public class ImportRoleCmdTest {
private ImportRoleCmd importRoleCmd;
private RoleService roleService;
private Role role;
@Before
public void setUp() {
roleService = Mockito.spy(RoleService.class);
importRoleCmd = new ImportRoleCmd();
ReflectionTestUtils.setField(importRoleCmd,"roleService",roleService);
ReflectionTestUtils.setField(importRoleCmd,"roleName","Test User");
ReflectionTestUtils.setField(importRoleCmd,"roleType", "User");
ReflectionTestUtils.setField(importRoleCmd,"roleDescription","test user imported");
role = Mockito.mock(Role.class);
}
@Test
public void testImportRoleSuccess() {
Map<String, Map<String, String>> rules = new HashMap<String, Map<String, String>>();
//Rule 1
Map<String, String> rule1 = new HashMap<String, String>();
rule1.put(ApiConstants.RULE, "list*");
rule1.put(ApiConstants.PERMISSION, "allow");
rule1.put(ApiConstants.DESCRIPTION, "listing apis");
rules.put("key1", rule1);
//Rule 2
Map<String, String> rule2 = new HashMap<String, String>();
rule2.put(ApiConstants.RULE, "update*");
rule2.put(ApiConstants.PERMISSION, "deny");
rule2.put(ApiConstants.DESCRIPTION, "no update allowed");
rules.put("key2", rule2);
//Rule 3
Map<String, String> rule3 = new HashMap<String, String>();
rule3.put(ApiConstants.RULE, "get*");
rule3.put(ApiConstants.PERMISSION, "allow");
rule3.put(ApiConstants.DESCRIPTION, "get details");
rules.put("key3", rule3);
ReflectionTestUtils.setField(importRoleCmd,"rules",rules);
when(role.getUuid()).thenReturn("12345-abcgdkajd");
when(role.getDescription()).thenReturn("test user imported");
when(role.getName()).thenReturn("Test User");
when(role.getRoleType()).thenReturn(RoleType.User);
when(roleService.importRole(anyString(),any(), anyString(), any(), anyBoolean())).thenReturn(role);
importRoleCmd.execute();
RoleResponse response = (RoleResponse) importRoleCmd.getResponseObject();
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), role.getName());
Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleDescription"), role.getDescription());
}
@Test(expected = InvalidParameterValueException.class)
public void testImportRoleInvalidRule() {
Map<String, Map<String, String>> rules = new HashMap<String, Map<String, String>>();
Map<String, String> rule = new HashMap<String, String>();
rule.put(ApiConstants.RULE, "*?+test*");
rule.put(ApiConstants.PERMISSION, "allow");
rule.put(ApiConstants.DESCRIPTION, "listing apis");
rules.put("key1", rule);
ReflectionTestUtils.setField(importRoleCmd,"rules",rules);
importRoleCmd.execute();
Assert.fail("An exception should have been thrown: " + InvalidParameterValueException.class);
}
@Test(expected = ServerApiException.class)
public void testImportRoleInvalidPermission() {
Map<String, Map<String, String>> rules = new HashMap<String, Map<String, String>>();
Map<String, String> rule = new HashMap<String, String>();
rule.put(ApiConstants.RULE, "list*");
rule.put(ApiConstants.PERMISSION, "pass");
rule.put(ApiConstants.DESCRIPTION, "listing apis");
rules.put("key1", rule);
ReflectionTestUtils.setField(importRoleCmd,"rules",rules);
importRoleCmd.execute();
Assert.fail("An exception should have been thrown: " + ServerApiException.class);
}
}

View File

@ -22,8 +22,11 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -65,6 +68,7 @@ public class Upgrade41400to41500 implements DbUpgrade {
@Override
public void performDataMigration(Connection conn) {
updateSystemVmTemplates(conn);
addRolePermissionsForNewReadOnlyAndSupportRoles(conn);
}
@SuppressWarnings("serial")
@ -235,6 +239,288 @@ public class Upgrade41400to41500 implements DbUpgrade {
LOG.debug("Updating System Vm Template IDs Complete");
}
private void addRolePermissionsForNewReadOnlyAndSupportRoles(final Connection conn) {
addRolePermissionsForReadOnlyAdmin(conn);
addRolePermissionsForReadOnlyUser(conn);
addRolePermissionsForSupportAdmin(conn);
addRolePermissionsForSupportUser(conn);
}
private void addRolePermissionsForReadOnlyAdmin(final Connection conn) {
LOG.debug("Adding role permissions for new read-only admin role");
try {
PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only Admin - Default' AND is_default = 1");
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
long readOnlyAdminRoleId = rs.getLong(1);
int readOnlyAdminSortOrder = 0;
Map<String, String> readOnlyAdminRules = new LinkedHashMap<>();
readOnlyAdminRules.put("list*", "ALLOW");
readOnlyAdminRules.put("getUploadParamsFor*", "DENY");
readOnlyAdminRules.put("get*", "ALLOW");
readOnlyAdminRules.put("cloudianIsEnabled", "ALLOW");
readOnlyAdminRules.put("queryAsyncJobResult", "ALLOW");
readOnlyAdminRules.put("quotaIsEnabled", "ALLOW");
readOnlyAdminRules.put("quotaTariffList", "ALLOW");
readOnlyAdminRules.put("quotaSummary", "ALLOW");
readOnlyAdminRules.put("*", "DENY");
for (Map.Entry<String, String> readOnlyAdminRule : readOnlyAdminRules.entrySet()) {
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, ?, ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, readOnlyAdminRoleId);
pstmt.setString(2, readOnlyAdminRule.getKey());
pstmt.setString(3, readOnlyAdminRule.getValue());
pstmt.setLong(4, readOnlyAdminSortOrder++);
pstmt.executeUpdate();
}
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (pstmt != null && !pstmt.isClosed()) {
pstmt.close();
}
LOG.debug("Successfully added role permissions for new read-only admin role");
} catch (final SQLException e) {
LOG.error("Exception while adding role permissions for read-only admin role: " + e.getMessage());
throw new CloudRuntimeException("Exception while adding role permissions for read-only admin role: " + e.getMessage(), e);
}
}
private void addRolePermissionsForReadOnlyUser(final Connection conn) {
LOG.debug("Adding role permissions for new read-only user role");
try {
PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only User - Default' AND is_default = 1");
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
long readOnlyUserRoleId = rs.getLong(1);
int readOnlyUserSortOrder = 0;
pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = 4 AND permission = 'ALLOW' AND rule LIKE 'list%' ORDER BY sort_order");
ResultSet rsRolePermissions = pstmt.executeQuery();
while (rsRolePermissions.next()) {
String rule = rsRolePermissions.getString(1);
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, readOnlyUserRoleId);
pstmt.setString(2, rule);
pstmt.setLong(3, readOnlyUserSortOrder++);
pstmt.executeUpdate();
}
pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = 4 AND permission = 'ALLOW' AND rule LIKE 'get%' AND rule NOT LIKE 'getUploadParamsFor%' ORDER BY sort_order");
rsRolePermissions = pstmt.executeQuery();
while (rsRolePermissions.next()) {
String rule = rsRolePermissions.getString(1);
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, readOnlyUserRoleId);
pstmt.setString(2, rule);
pstmt.setLong(3, readOnlyUserSortOrder++);
pstmt.executeUpdate();
}
List<String> readOnlyUserRulesAllowed = new ArrayList<>();
readOnlyUserRulesAllowed.add("cloudianIsEnabled");
readOnlyUserRulesAllowed.add("queryAsyncJobResult");
readOnlyUserRulesAllowed.add("quotaIsEnabled");
readOnlyUserRulesAllowed.add("quotaTariffList");
readOnlyUserRulesAllowed.add("quotaSummary");
for(String readOnlyUserRule : readOnlyUserRulesAllowed) {
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, readOnlyUserRoleId);
pstmt.setString(2, readOnlyUserRule);
pstmt.setLong(3, readOnlyUserSortOrder++);
pstmt.executeUpdate();
}
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, readOnlyUserRoleId);
pstmt.setLong(2, readOnlyUserSortOrder);
pstmt.executeUpdate();
if (rsRolePermissions != null && !rsRolePermissions.isClosed()) {
rsRolePermissions.close();
}
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (pstmt != null && !pstmt.isClosed()) {
pstmt.close();
}
LOG.debug("Successfully added role permissions for new read-only user role");
} catch (final SQLException e) {
LOG.error("Exception while adding role permissions for read-only user role: " + e.getMessage());
throw new CloudRuntimeException("Exception while adding role permissions for read-only user role: " + e.getMessage(), e);
}
}
private void addRolePermissionsForSupportAdmin(final Connection conn) {
LOG.debug("Adding role permissions for new support admin role");
try {
PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Support Admin - Default' AND is_default = 1");
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
long supportAdminRoleId = rs.getLong(1);
int supportAdminSortOrder = 0;
pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only Admin - Default' AND is_default = 1");
ResultSet rsReadOnlyAdmin = pstmt.executeQuery();
if (rsReadOnlyAdmin.next()) {
long readOnlyAdminRoleId = rsReadOnlyAdmin.getLong(1);
pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = ? AND permission = 'ALLOW' ORDER BY sort_order");
pstmt.setLong(1, readOnlyAdminRoleId);
ResultSet rsRolePermissions = pstmt.executeQuery();
while (rsRolePermissions.next()) {
String rule = rsRolePermissions.getString(1);
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportAdminRoleId);
pstmt.setString(2, rule);
pstmt.setLong(3, supportAdminSortOrder++);
pstmt.executeUpdate();
}
List<String> supportAdminRulesAllowed = new ArrayList<>();
supportAdminRulesAllowed.add("prepareHostForMaintenance");
supportAdminRulesAllowed.add("cancelHostMaintenance");
supportAdminRulesAllowed.add("enableStorageMaintenance");
supportAdminRulesAllowed.add("cancelStorageMaintenance");
supportAdminRulesAllowed.add("createServiceOffering");
supportAdminRulesAllowed.add("createDiskOffering");
supportAdminRulesAllowed.add("createNetworkOffering");
supportAdminRulesAllowed.add("createVPCOffering");
supportAdminRulesAllowed.add("startVirtualMachine");
supportAdminRulesAllowed.add("stopVirtualMachine");
supportAdminRulesAllowed.add("rebootVirtualMachine");
supportAdminRulesAllowed.add("startKubernetesCluster");
supportAdminRulesAllowed.add("stopKubernetesCluster");
supportAdminRulesAllowed.add("createVolume");
supportAdminRulesAllowed.add("attachVolume");
supportAdminRulesAllowed.add("detachVolume");
supportAdminRulesAllowed.add("uploadVolume");
supportAdminRulesAllowed.add("attachIso");
supportAdminRulesAllowed.add("detachIso");
supportAdminRulesAllowed.add("registerTemplate");
supportAdminRulesAllowed.add("registerIso");
for(String supportAdminRule : supportAdminRulesAllowed) {
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportAdminRoleId);
pstmt.setString(2, supportAdminRule);
pstmt.setLong(3, supportAdminSortOrder++);
pstmt.executeUpdate();
}
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportAdminRoleId);
pstmt.setLong(2, supportAdminSortOrder);
pstmt.executeUpdate();
if (rsRolePermissions != null && !rsRolePermissions.isClosed()) {
rsRolePermissions.close();
}
}
if (rsReadOnlyAdmin != null && !rsReadOnlyAdmin.isClosed()) {
rsReadOnlyAdmin.close();
}
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (pstmt != null && !pstmt.isClosed()) {
pstmt.close();
}
LOG.debug("Successfully added role permissions for new support admin role");
} catch (final SQLException e) {
LOG.error("Exception while adding role permissions for support admin role: " + e.getMessage());
throw new CloudRuntimeException("Exception while adding role permissions for support admin role: " + e.getMessage(), e);
}
}
private void addRolePermissionsForSupportUser(final Connection conn) {
LOG.debug("Adding role permissions for new support user role");
try {
PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Support User - Default' AND is_default = 1");
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
long supportUserRoleId = rs.getLong(1);
int supportUserSortOrder = 0;
pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only User - Default' AND is_default = 1");
ResultSet rsReadOnlyUser = pstmt.executeQuery();
if (rsReadOnlyUser.next()) {
long readOnlyUserRoleId = rsReadOnlyUser.getLong(1);
pstmt = conn.prepareStatement("SELECT rule FROM `cloud`.`role_permissions` WHERE role_id = ? AND permission = 'ALLOW' ORDER BY sort_order");
pstmt.setLong(1, readOnlyUserRoleId);
ResultSet rsRolePermissions = pstmt.executeQuery();
while (rsRolePermissions.next()) {
String rule = rsRolePermissions.getString(1);
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportUserRoleId);
pstmt.setString(2, rule);
pstmt.setLong(3, supportUserSortOrder++);
pstmt.executeUpdate();
}
List<String> supportUserRulesAllowed = new ArrayList<>();
supportUserRulesAllowed.add("startVirtualMachine");
supportUserRulesAllowed.add("stopVirtualMachine");
supportUserRulesAllowed.add("rebootVirtualMachine");
supportUserRulesAllowed.add("startKubernetesCluster");
supportUserRulesAllowed.add("stopKubernetesCluster");
supportUserRulesAllowed.add("createVolume");
supportUserRulesAllowed.add("attachVolume");
supportUserRulesAllowed.add("detachVolume");
supportUserRulesAllowed.add("uploadVolume");
supportUserRulesAllowed.add("attachIso");
supportUserRulesAllowed.add("detachIso");
supportUserRulesAllowed.add("registerTemplate");
supportUserRulesAllowed.add("registerIso");
supportUserRulesAllowed.add("getUploadParamsFor*");
for(String supportUserRule : supportUserRulesAllowed) {
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, ?, 'ALLOW', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportUserRoleId);
pstmt.setString(2, supportUserRule);
pstmt.setLong(3, supportUserSortOrder++);
pstmt.executeUpdate();
}
pstmt = conn.prepareStatement("INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) VALUES (UUID(), ?, '*', 'DENY', ?) ON DUPLICATE KEY UPDATE rule=rule");
pstmt.setLong(1, supportUserRoleId);
pstmt.setLong(2, supportUserSortOrder);
pstmt.executeUpdate();
if (rsRolePermissions != null && !rsRolePermissions.isClosed()) {
rsRolePermissions.close();
}
}
if (rsReadOnlyUser != null && !rsReadOnlyUser.isClosed()) {
rsReadOnlyUser.close();
}
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (pstmt != null && !pstmt.isClosed()) {
pstmt.close();
}
LOG.debug("Successfully added role permissions for new support user role");
} catch (final SQLException e) {
LOG.error("Exception while adding role permissions for support user role: " + e.getMessage());
throw new CloudRuntimeException("Exception while adding role permissions for support user role: " + e.getMessage(), e);
}
}
@Override
public InputStream[] getCleanupScripts() {
final String scriptFile = "META-INF/db/schema-41400to41500-cleanup.sql";

View File

@ -51,6 +51,9 @@ public class RoleVO implements Role {
@Column(name = "description")
private String description;
@Column(name = "is_default")
private boolean isDefault = false;
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
@ -75,6 +78,10 @@ public class RoleVO implements Role {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public long getId() {
return id;
@ -103,4 +110,8 @@ public class RoleVO implements Role {
public void setDescription(String description) {
this.description = description;
}
public boolean isDefault() {
return isDefault;
}
}

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.RoleVO;
@ -26,4 +27,6 @@ import java.util.List;
public interface RoleDao extends GenericDao<RoleVO, Long> {
List<RoleVO> findAllByName(String roleName);
List<RoleVO> findAllByRoleType(RoleType type);
List<RoleVO> findByName(String roleName);
RoleVO findByNameAndType(String roleName, RoleType type);
}

View File

@ -20,6 +20,7 @@ package org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.RoleVO;
import org.springframework.stereotype.Component;
@ -30,6 +31,7 @@ import java.util.List;
public class RoleDaoImpl extends GenericDaoBase<RoleVO, Long> implements RoleDao {
private final SearchBuilder<RoleVO> RoleByNameSearch;
private final SearchBuilder<RoleVO> RoleByTypeSearch;
private final SearchBuilder<RoleVO> RoleByNameAndTypeSearch;
public RoleDaoImpl() {
super();
@ -41,6 +43,11 @@ public class RoleDaoImpl extends GenericDaoBase<RoleVO, Long> implements RoleDao
RoleByTypeSearch = createSearchBuilder();
RoleByTypeSearch.and("roleType", RoleByTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ);
RoleByTypeSearch.done();
RoleByNameAndTypeSearch = createSearchBuilder();
RoleByNameAndTypeSearch.and("roleName", RoleByNameAndTypeSearch.entity().getName(), SearchCriteria.Op.EQ);
RoleByNameAndTypeSearch.and("roleType", RoleByNameAndTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ);
RoleByNameAndTypeSearch.done();
}
@Override
@ -56,4 +63,19 @@ public class RoleDaoImpl extends GenericDaoBase<RoleVO, Long> implements RoleDao
sc.setParameters("roleType", type);
return listBy(sc);
}
@Override
public List<RoleVO> findByName(String roleName) {
SearchCriteria<RoleVO> sc = RoleByNameSearch.create();
sc.setParameters("roleName", roleName);
return listBy(sc);
}
@Override
public RoleVO findByNameAndType(String roleName, RoleType type) {
SearchCriteria<RoleVO> sc = RoleByNameAndTypeSearch.create();
sc.setParameters("roleName", roleName);
sc.setParameters("roleType", type);
return findOneBy(sc);
}
}

View File

@ -56,4 +56,12 @@ public interface RolePermissionsDao extends GenericDao<RolePermissionVO, Long> {
* @return returns list of role permissions
*/
List<RolePermissionVO> findAllByRoleIdSorted(Long roleId);
/**
* Returns role permission for a given role and rule
* @param roleId the ID of the role
* @param roleId rule for the role
* @return returns role permission
*/
RolePermissionVO findByRoleIdAndRule(Long roleId, String rule);
}

View File

@ -44,12 +44,18 @@ import java.util.Set;
public class RolePermissionsDaoImpl extends GenericDaoBase<RolePermissionVO, Long> implements RolePermissionsDao {
protected static final Logger LOGGER = Logger.getLogger(RolePermissionsDaoImpl.class);
private final SearchBuilder<RolePermissionVO> RolePermissionsSearchByRoleAndRule;
private final SearchBuilder<RolePermissionVO> RolePermissionsSearch;
private Attribute sortOrderAttribute;
public RolePermissionsDaoImpl() {
super();
RolePermissionsSearchByRoleAndRule = createSearchBuilder();
RolePermissionsSearchByRoleAndRule.and("roleId", RolePermissionsSearchByRoleAndRule.entity().getRoleId(), SearchCriteria.Op.EQ);
RolePermissionsSearchByRoleAndRule.and("rule", RolePermissionsSearchByRoleAndRule.entity().getRule(), SearchCriteria.Op.EQ);
RolePermissionsSearchByRoleAndRule.done();
RolePermissionsSearch = createSearchBuilder();
RolePermissionsSearch.and("uuid", RolePermissionsSearch.entity().getUuid(), SearchCriteria.Op.EQ);
RolePermissionsSearch.and("roleId", RolePermissionsSearch.entity().getRoleId(), SearchCriteria.Op.EQ);
@ -174,4 +180,12 @@ public class RolePermissionsDaoImpl extends GenericDaoBase<RolePermissionVO, Lon
}
return rolePermissionList;
}
@Override
public RolePermissionVO findByRoleIdAndRule(final Long roleId, final String rule) {
final SearchCriteria<RolePermissionVO> sc = RolePermissionsSearchByRoleAndRule.create();
sc.setParameters("roleId", roleId);
sc.setParameters("rule", rule);
return findOneBy(sc);
}
}

View File

@ -18,3 +18,6 @@
--;
-- Schema upgrade cleanup from 4.14.0.0 to 4.15.0.0
--;
-- remove the old NetApp storage APIs (unsupported since 4.12) from role_permissions
DELETE from `cloud`.`role_permissions` WHERE rule IN ('createPool', 'modifyPool', 'deletePool', 'listPools', 'associateLun', 'dissociateLun', 'createLunOnFiler', 'destroyLunOnFiler', 'listLunsOnFiler', 'createVolumeOnFiler', 'destroyVolumeOnFiler', 'listVolumesOnFiler');

View File

@ -18,3 +18,12 @@
--;
-- Schema upgrade from 4.14.0.0 to 4.15.0.0
--;
ALTER TABLE `cloud`.`roles` ADD COLUMN `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'is this a default role';
UPDATE `cloud`.`roles` SET `is_default` = 1 WHERE id IN (1, 2, 3, 4);
-- Updated Default CloudStack roles with read-only and support admin and user roles
INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Read-Only Admin - Default', 'Admin', 'Default read-only admin role', 1);
INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Read-Only User - Default', 'User', 'Default read-only user role', 1);
INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Support Admin - Default', 'Admin', 'Default support admin role', 1);
INSERT INTO `cloud`.`roles` (`uuid`, `name`, `role_type`, `description`, `is_default`) VALUES (UUID(), 'Support User - Default', 'User', 'Default support user role', 1);

View File

@ -1204,6 +1204,17 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
}
}
@Override
public boolean isValidApiName(String apiName) {
if (apiName == null || apiName.isEmpty())
return false;
if (!s_apiNameCmdClassMap.containsKey(apiName))
return false;
return true;
}
// FIXME: rather than isError, we might was to pass in the status code to give more flexibility
private void writeResponse(final HttpResponse resp, final String responseText, final int statusCode, final String responseType, final String reasonPhrase) {
try {

View File

@ -20,17 +20,20 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.cloudstack.acl.dao.RoleDao;
import org.apache.cloudstack.acl.dao.RolePermissionsDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.CreateRolePermissionCmd;
import org.apache.cloudstack.api.command.admin.acl.DeleteRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.DeleteRolePermissionCmd;
import org.apache.cloudstack.api.command.admin.acl.ImportRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.ListRolePermissionsCmd;
import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd;
import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd;
@ -54,6 +57,7 @@ import com.cloud.utils.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 RoleManagerImpl extends ManagerBase implements RoleService, Configurable, PluggableService {
@ -124,11 +128,12 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
}
@Override
public RolePermission findRolePermissionByUuid(final String uuid) {
if (Strings.isNullOrEmpty(uuid)) {
public RolePermission findRolePermissionByRoleIdAndRule(final Long roleId, final String rule) {
if (roleId == null || Strings.isNullOrEmpty(rule)) {
return null;
}
return rolePermissionsDao.findByUuid(uuid);
return rolePermissionsDao.findByRoleIdAndRule(roleId, rule);
}
@Override
@ -146,10 +151,99 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_CREATE, eventDescription = "creating role by cloning another role")
public Role createRole(String name, Role role, String description) {
checkCallerAccess();
return Transaction.execute(new TransactionCallback<RoleVO>() {
@Override
public RoleVO doInTransaction(TransactionStatus status) {
RoleVO newRoleVO = roleDao.persist(new RoleVO(name, role.getRoleType(), description));
if (newRoleVO == null) {
throw new CloudRuntimeException("Unable to create the role: " + name + ", failed to persist in DB");
}
List<RolePermissionVO> rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(role.getId());
if (rolePermissions != null && !rolePermissions.isEmpty()) {
for (RolePermissionVO permission : rolePermissions) {
rolePermissionsDao.persist(new RolePermissionVO(newRoleVO.getId(), permission.getRule().toString(), permission.getPermission(), permission.getDescription()));
}
}
return newRoleVO;
}
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_IMPORT, eventDescription = "importing Role")
public Role importRole(String name, RoleType type, String description, List<Map<String, Object>> rules, boolean forced) {
checkCallerAccess();
if (Strings.isNullOrEmpty(name)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role name provided");
}
if (type == null || type == RoleType.Unknown) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided");
}
List<RoleVO> existingRoles = roleDao.findByName(name);
if (CollectionUtils.isNotEmpty(existingRoles) && !forced) {
throw new CloudRuntimeException("Role already exists");
}
return Transaction.execute(new TransactionCallback<RoleVO>() {
@Override
public RoleVO doInTransaction(TransactionStatus status) {
RoleVO newRole = null;
RoleVO existingRole = roleDao.findByNameAndType(name, type);
if (existingRole != null) {
if (existingRole.isDefault()) {
throw new CloudRuntimeException("Failed to import the role: " + name + ", default role cannot be overriden");
}
//Cleanup old role permissions
List<? extends RolePermission> rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(existingRole.getId());
if (rolePermissions != null && !rolePermissions.isEmpty()) {
for (RolePermission rolePermission : rolePermissions) {
rolePermissionsDao.remove(rolePermission.getId());
}
}
existingRole.setName(name);
existingRole.setRoleType(type);
existingRole.setDescription(description);
roleDao.update(existingRole.getId(), existingRole);
newRole = existingRole;
} else {
newRole = roleDao.persist(new RoleVO(name, type, description));
}
if (newRole == null) {
throw new CloudRuntimeException("Unable to import the role: " + name + ", failed to persist in DB");
}
if (rules != null && !rules.isEmpty()) {
for (Map<String, Object> ruleDetail : rules) {
Rule rule = (Rule)ruleDetail.get(ApiConstants.RULE);
RolePermission.Permission rulePermission = (RolePermission.Permission) ruleDetail.get(ApiConstants.PERMISSION);
String ruleDescription = (String) ruleDetail.get(ApiConstants.DESCRIPTION);
rolePermissionsDao.persist(new RolePermissionVO(newRole.getId(), rule.toString(), rulePermission, ruleDescription));
}
}
return newRole;
}
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_UPDATE, eventDescription = "updating Role")
public Role updateRole(final Role role, final String name, final RoleType roleType, final String description) {
checkCallerAccess();
if (role.isDefault()) {
throw new PermissionDeniedException("Default roles cannot be updated");
}
if (roleType != null && roleType == RoleType.Unknown) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unknown is not a valid role type");
@ -159,9 +253,6 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
roleVO.setName(name);
}
if (roleType != null) {
if (role.getId() <= RoleType.User.getId()) {
throw new PermissionDeniedException("The role type of default roles cannot be changed");
}
List<? extends Account> accounts = accountDao.findAccountsByRole(role.getId());
if (accounts == null || accounts.isEmpty()) {
roleVO.setRoleType(roleType);
@ -184,7 +275,7 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
if (role == null) {
return false;
}
if (role.getId() <= RoleType.User.getId()) {
if (role.isDefault()) {
throw new PermissionDeniedException("Default roles cannot be deleted");
}
List<? extends Account> accounts = accountDao.findAccountsByRole(role.getId());
@ -214,6 +305,14 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_CREATE, eventDescription = "creating Role Permission")
public RolePermission createRolePermission(final Role role, final Rule rule, final RolePermission.Permission permission, final String description) {
checkCallerAccess();
if (role.isDefault()) {
throw new PermissionDeniedException("Role permission cannot be added for Default roles");
}
if (findRolePermissionByRoleIdAndRule(role.getId(), rule.toString()) != null) {
throw new PermissionDeniedException("Rule already exists for the role: " + role.getName());
}
return Transaction.execute(new TransactionCallback<RolePermissionVO>() {
@Override
public RolePermissionVO doInTransaction(TransactionStatus status) {
@ -226,12 +325,18 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_UPDATE, eventDescription = "updating Role Permission order")
public boolean updateRolePermission(final Role role, final List<RolePermission> newOrder) {
checkCallerAccess();
if (role.isDefault()) {
throw new PermissionDeniedException("Role permission cannot be updated for Default roles");
}
return role != null && newOrder != null && rolePermissionsDao.update(role, newOrder);
}
@Override
public boolean updateRolePermission(Role role, RolePermission rolePermission, RolePermission.Permission permission) {
checkCallerAccess();
if (role.isDefault()) {
throw new PermissionDeniedException("Role permission cannot be updated for Default roles");
}
return role != null && rolePermissionsDao.update(role, rolePermission, permission);
}
@ -239,6 +344,10 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_DELETE, eventDescription = "deleting Role Permission")
public boolean deleteRolePermission(final RolePermission rolePermission) {
checkCallerAccess();
Role role = findRole(rolePermission.getRoleId());
if (role.isDefault()) {
throw new PermissionDeniedException("Role permission cannot be deleted for Default roles");
}
return rolePermission != null && rolePermissionsDao.remove(rolePermission.getId());
}
@ -277,7 +386,6 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
rolesIterator.remove();
}
}
}
@Override
@ -305,6 +413,18 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
return Collections.emptyList();
}
@Override
public RolePermission.Permission getRolePermission(String permission) {
if (Strings.isNullOrEmpty(permission)) {
return null;
}
if (!permission.equalsIgnoreCase(RolePermission.Permission.ALLOW.toString()) &&
!permission.equalsIgnoreCase(RolePermission.Permission.DENY.toString())) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Values for permission parameter should be: allow or deny");
}
return permission.equalsIgnoreCase(RolePermission.Permission.ALLOW.toString()) ? RolePermission.Permission.ALLOW : RolePermission.Permission.DENY;
}
@Override
public String getConfigComponentName() {
return RoleService.class.getSimpleName();
@ -319,6 +439,7 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(CreateRoleCmd.class);
cmdList.add(ImportRoleCmd.class);
cmdList.add(ListRolesCmd.class);
cmdList.add(UpdateRoleCmd.class);
cmdList.add(DeleteRoleCmd.class);

View File

@ -45,6 +45,14 @@ class TestData(object):
"type": "User",
"description": "Fake Role created by Marvin test"
},
"importrole": {
"name": "MarvinFake Import Role ",
"type": "User",
"description": "Fake Import User Role created by Marvin test",
"rules" : [{"rule":"list*", "permission":"allow","description":"Listing apis"},
{"rule":"get*", "permission":"allow","description":"Get apis"},
{"rule":"update*", "permission":"deny","description":"Update apis"}]
},
"roleadmin": {
"name": "MarvinFake Admin Role ",
"type": "Admin",
@ -201,6 +209,91 @@ class TestDynamicRoles(cloudstackTestCase):
msg="Role type does not match the test data"
)
@attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
def test_role_lifecycle_clone(self):
"""
Tests create role from existing role
"""
# Use self.role created in setUp()
role_to_be_cloned = {
"name": "MarvinFake Clone Role ",
"roleid": self.role.id,
"description": "Fake Role cloned by Marvin test"
}
try:
role_cloned = Role.create(
self.apiclient,
role_to_be_cloned
)
self.cleanup.append(role_cloned)
except CloudstackAPIException as e:
self.fail("Failed to create the role: %s" % e)
list_role_cloned= Role.list(self.apiclient, id=role_cloned.id)
self.assertEqual(
isinstance(list_role_cloned, list),
True,
"List Roles response was not a valid list"
)
self.assertEqual(
len(list_role_cloned),
1,
"List Roles response size was not 1"
)
self.assertEqual(
list_role_cloned[0].name,
role_to_be_cloned["name"],
msg="Role name does not match the test data"
)
self.assertEqual(
list_role_cloned[0].type,
self.testdata["role"]["type"],
msg="Role type does not match the test data"
)
list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id)
self.validate_permissions_list(list_rolepermissions, role_cloned.id)
@attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
def test_role_lifecycle_import(self):
"""
Tests import role with the rules
"""
# use importrole from testdata
self.testdata["importrole"]["name"] += self.getRandomString()
try:
role_imported = Role.importRole(
self.apiclient,
self.testdata["importrole"]
)
self.cleanup.append(role_imported)
except CloudstackAPIException as e:
self.fail("Failed to import the role: %s" % e)
list_role_imported = Role.list(self.apiclient, id=role_imported.id)
self.assertEqual(
isinstance(list_role_imported, list),
True,
"List Roles response was not a valid list"
)
self.assertEqual(
len(list_role_imported),
1,
"List Roles response size was not 1"
)
self.assertEqual(
list_role_imported[0].name,
self.testdata["importrole"]["name"],
msg="Role name does not match the test data"
)
self.assertEqual(
list_role_imported[0].type,
self.testdata["importrole"]["type"],
msg="Role type does not match the test data"
)
self.validate_permissions_dict(self.testdata["importrole"]["rules"], role_imported.id)
@attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
def test_role_lifecycle_update(self):
@ -360,44 +453,23 @@ class TestDynamicRoles(cloudstackTestCase):
self.cleanup.append(permission)
permissions.append(permission)
def validate_permissions_list(permissions):
list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id)
self.assertEqual(
len(list_rolepermissions),
len(permissions),
msg="List of role permissions do not match created list of permissions"
)
for idx, rolepermission in enumerate(list_rolepermissions):
self.assertEqual(
rolepermission.rule,
permissions[idx].rule,
msg="Rule permission don't match with expected item at the index"
)
self.assertEqual(
rolepermission.permission,
permissions[idx].permission,
msg="Rule permission don't match with expected item at the index"
)
# Move last item to the top
rule = permissions.pop(len(permissions)-1)
permissions = [rule] + permissions
rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
validate_permissions_list(permissions)
self.validate_permissions_list(permissions, self.role.id)
# Move to the bottom
rule = permissions.pop(0)
permissions = permissions + [rule]
rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
validate_permissions_list(permissions)
self.validate_permissions_list(permissions, self.role.id)
# Random shuffles
for _ in range(3):
shuffle(permissions)
rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
validate_permissions_list(permissions)
self.validate_permissions_list(permissions, self.role.id)
@attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
def test_rolepermission_lifecycle_update_permission(self):
@ -580,3 +652,43 @@ class TestDynamicRoles(cloudstackTestCase):
# Perform actual API call for allow API
self.checkApiCall(apiConfig, userApiClient)
def validate_permissions_list(self, permissions, roleid):
list_rolepermissions = RolePermission.list(self.apiclient, roleid=roleid)
self.assertEqual(
len(list_rolepermissions),
len(permissions),
msg="List of role permissions do not match created list of permissions"
)
for idx, rolepermission in enumerate(list_rolepermissions):
self.assertEqual(
rolepermission.rule,
permissions[idx].rule,
msg="Rule permission don't match with expected item at the index"
)
self.assertEqual(
rolepermission.permission,
permissions[idx].permission,
msg="Rule permission don't match with expected item at the index"
)
def validate_permissions_dict(self, permissions, roleid):
list_rolepermissions = RolePermission.list(self.apiclient, roleid=roleid)
self.assertEqual(
len(list_rolepermissions),
len(permissions),
msg="List of role permissions do not match created list of permissions"
)
for idx, rolepermission in enumerate(list_rolepermissions):
self.assertEqual(
rolepermission.rule,
permissions[idx]["rule"],
msg="Rule permission don't match with expected item at the index"
)
self.assertEqual(
rolepermission.permission,
permissions[idx]["permission"],
msg="Rule permission don't match with expected item at the index"
)

View File

@ -102,12 +102,29 @@ class Role:
"""Create role"""
cmd = createRole.createRoleCmd()
cmd.name = services["name"]
if "type" in services:
cmd.type = services["type"]
if "roleid" in services:
cmd.roleid = services["roleid"]
if "description" in services:
cmd.description = services["description"]
return Role(apiclient.createRole(cmd).__dict__)
@classmethod
def importRole(cls, apiclient, services, domainid=None):
"""Import role"""
cmd = importRole.importRoleCmd()
cmd.name = services["name"]
cmd.type = services["type"]
cmd.rules = services["rules"]
if "description" in services:
cmd.description = services["description"]
if "forced" in services:
cmd.type = services["forced"]
return Role(apiclient.importRole(cmd).__dict__)
def delete(self, apiclient):
"""Delete Role"""