You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2016/05/11 09:43:27 UTC
[39/50] [abbrv] git commit: updated refs/heads/4.9-bountycastle-daan
to 4817cb6
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/api/src/org/apache/cloudstack/api/response/UserResponse.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/response/UserResponse.java b/api/src/org/apache/cloudstack/api/response/UserResponse.java
index 36611ae..cc986b3 100644
--- a/api/src/org/apache/cloudstack/api/response/UserResponse.java
+++ b/api/src/org/apache/cloudstack/api/response/UserResponse.java
@@ -20,6 +20,7 @@ import java.util.Date;
import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
@@ -65,6 +66,18 @@ public class UserResponse extends BaseResponse {
@Param(description = "the account type of the user")
private Short accountType;
+ @SerializedName(ApiConstants.ROLE_ID)
+ @Param(description = "the ID of the role")
+ private String roleId;
+
+ @SerializedName(ApiConstants.ROLE_TYPE)
+ @Param(description = "the type of the role")
+ private String roleType;
+
+ @SerializedName(ApiConstants.ROLE_NAME)
+ @Param(description = "the name of the role")
+ private String roleName;
+
@SerializedName("domainid")
@Param(description = "the domain ID of the user")
private String domainId;
@@ -174,6 +187,20 @@ public class UserResponse extends BaseResponse {
this.accountType = accountType;
}
+ public void setRoleId(String roleId) {
+ this.roleId = roleId;
+ }
+
+ public void setRoleType(RoleType roleType) {
+ if (roleType != null) {
+ this.roleType = roleType.name();
+ }
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
public String getDomainId() {
return domainId;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/api/test/org/apache/cloudstack/acl/RoleTypeTest.java
----------------------------------------------------------------------
diff --git a/api/test/org/apache/cloudstack/acl/RoleTypeTest.java b/api/test/org/apache/cloudstack/acl/RoleTypeTest.java
new file mode 100644
index 0000000..611b761
--- /dev/null
+++ b/api/test/org/apache/cloudstack/acl/RoleTypeTest.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 com.cloud.user.Account;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+
+public class RoleTypeTest {
+
+ @Test
+ public void testValidRoleTypeFromString() {
+ for (RoleType roleType : RoleType.values()) {
+ Assert.assertEquals(RoleType.fromString(roleType.name()), roleType);
+ }
+ }
+
+ @Test
+ public void testInvalidRoleTypeFromString() {
+ for (String roleType : Arrays.asList(null, "", "admin", "12345%&^*")) {
+ try {
+ RoleType.fromString(roleType);
+ Assert.fail("Invalid roletype provided, exception was expected");
+ } catch (IllegalStateException e) {
+ Assert.assertEquals(e.getMessage(), "Illegal RoleType name provided");
+ }
+ }
+ }
+
+ @Test
+ public void testDefaultRoleMaskByValue() {
+ Assert.assertEquals(RoleType.fromMask(1), RoleType.Admin);
+ Assert.assertEquals(RoleType.fromMask(2), RoleType.ResourceAdmin);
+ Assert.assertEquals(RoleType.fromMask(4), RoleType.DomainAdmin);
+ Assert.assertEquals(RoleType.fromMask(8), RoleType.User);
+ Assert.assertEquals(RoleType.fromMask(0), RoleType.Unknown);
+ }
+
+ @Test
+ public void testGetByAccountType() {
+ Assert.assertEquals(RoleType.getByAccountType(Account.ACCOUNT_TYPE_NORMAL), RoleType.User);
+ Assert.assertEquals(RoleType.getByAccountType(Account.ACCOUNT_TYPE_ADMIN), RoleType.Admin);
+ Assert.assertEquals(RoleType.getByAccountType(Account.ACCOUNT_TYPE_DOMAIN_ADMIN), RoleType.DomainAdmin);
+ Assert.assertEquals(RoleType.getByAccountType(Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN), RoleType.ResourceAdmin);
+ Assert.assertEquals(RoleType.getByAccountType(Account.ACCOUNT_TYPE_PROJECT), RoleType.Unknown);
+ }
+
+ @Test
+ public void testGetRoleByAccountTypeWhenRoleIdIsProvided() {
+ Assert.assertEquals(RoleType.getRoleByAccountType(123L, Account.ACCOUNT_TYPE_ADMIN), Long.valueOf(123L));
+ Assert.assertEquals(RoleType.getRoleByAccountType(1234L, null), Long.valueOf(1234L));
+ }
+
+ @Test
+ public void testGetRoleByAccountTypeForDefaultAccountTypes() {
+ Assert.assertEquals(RoleType.getRoleByAccountType(null, Account.ACCOUNT_TYPE_ADMIN), (Long) RoleType.Admin.getId());
+ Assert.assertEquals(RoleType.getRoleByAccountType(null, Account.ACCOUNT_TYPE_NORMAL), (Long) RoleType.User.getId());
+ Assert.assertEquals(RoleType.getRoleByAccountType(null, Account.ACCOUNT_TYPE_DOMAIN_ADMIN), (Long) RoleType.DomainAdmin.getId());
+ Assert.assertEquals(RoleType.getRoleByAccountType(null, Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN), (Long) RoleType.ResourceAdmin.getId());
+ Assert.assertEquals(RoleType.getRoleByAccountType(null, Account.ACCOUNT_TYPE_PROJECT), null);
+ }
+
+ @Test
+ public void testGetAccountTypeByRoleWhenRoleIsNull() {
+ for (Short accountType: Arrays.asList(
+ Account.ACCOUNT_TYPE_NORMAL,
+ Account.ACCOUNT_TYPE_ADMIN,
+ Account.ACCOUNT_TYPE_DOMAIN_ADMIN,
+ Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN,
+ Account.ACCOUNT_TYPE_PROJECT,
+ (short) 12345)) {
+ Assert.assertEquals(RoleType.getAccountTypeByRole(null, accountType), accountType);
+ }
+ }
+
+ @Test
+ public void testGetAccountTypeByRole() {
+ Role role = Mockito.mock(Role.class);
+ Mockito.when(role.getRoleType()).thenReturn(RoleType.Admin);
+ Mockito.when(role.getId()).thenReturn(100L);
+ Assert.assertEquals(RoleType.getAccountTypeByRole(role, null), (Short) RoleType.Admin.getAccountType());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/api/test/org/apache/cloudstack/acl/RuleTest.java
----------------------------------------------------------------------
diff --git a/api/test/org/apache/cloudstack/acl/RuleTest.java b/api/test/org/apache/cloudstack/acl/RuleTest.java
new file mode 100644
index 0000000..08bd775
--- /dev/null
+++ b/api/test/org/apache/cloudstack/acl/RuleTest.java
@@ -0,0 +1,98 @@
+// 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 com.cloud.exception.InvalidParameterValueException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class RuleTest {
+
+ @Test
+ public void testToString() throws Exception {
+ Rule rule = new Rule("someString");
+ Assert.assertEquals(rule.toString(), "someString");
+ }
+
+ @Test
+ public void testMatchesEmpty() throws Exception {
+ Rule rule = new Rule("someString");
+ Assert.assertFalse(rule.matches(""));
+ }
+
+ @Test
+ public void testMatchesNull() throws Exception {
+ Rule rule = new Rule("someString");
+ Assert.assertFalse(rule.matches(null));
+ }
+
+ @Test
+ public void testMatchesSpace() throws Exception {
+ Rule rule = new Rule("someString");
+ Assert.assertFalse(rule.matches(" "));
+ }
+
+ @Test
+ public void testMatchesAPI() throws Exception {
+ Rule rule = new Rule("someApi");
+ Assert.assertTrue(rule.matches("someApi"));
+ }
+
+ @Test
+ public void testMatchesWildcardSuffix() throws Exception {
+ Rule rule = new Rule("list*");
+ Assert.assertTrue(rule.matches("listHosts"));
+ }
+
+ @Test
+ public void testMatchesWildcardPrefix() throws Exception {
+ Rule rule = new Rule("*User");
+ Assert.assertTrue(rule.matches("createUser"));
+ }
+
+ @Test
+ public void testMatchesWildcardMiddle() throws Exception {
+ Rule rule = new Rule("list*s");
+ Assert.assertTrue(rule.matches("listClusters"));
+ }
+
+ @Test
+ public void testValidateRuleWithValidData() throws Exception {
+ for (String rule : Arrays.asList("a", "1", "someApi", "someApi321", "123SomeApi",
+ "prefix*", "*middle*", "*Suffix",
+ "*", "**", "f***", "m0nk3yMa**g1c*")) {
+ Assert.assertEquals(new Rule(rule).toString(), rule);
+ }
+ }
+
+ @Test
+ public void testValidateRuleWithInvalidData() throws Exception {
+ for (String rule : Arrays.asList(null, "", " ", " ", "\n", "\t", "\r", "\"", "\'",
+ "^someApi$", "^someApi", "some$", "some-Api;", "some,Api",
+ "^", "$", "^$", ".*", "\\w+", "r**l3rd0@Kr3", "j@s1n|+|0\u0237",
+ "[a-z0-9-]+", "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$")) {
+ try {
+ new Rule(rule);
+ Assert.fail("Invalid rule, exception was expected");
+ } catch (InvalidParameterValueException e) {
+ Assert.assertTrue(e.getMessage().startsWith("Only API names and wildcards are allowed"));
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/api/test/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
----------------------------------------------------------------------
diff --git a/api/test/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java b/api/test/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
index c0a046d..b50b226 100644
--- a/api/test/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
+++ b/api/test/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.cloudstack.api.command.admin.account;
+import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
@@ -41,10 +42,13 @@ public class CreateAccountCmdTest {
@Mock
private AccountService accountService;
+ @Mock
+ private RoleService roleService;
@InjectMocks
private CreateAccountCmd createAccountCmd = new CreateAccountCmd();
+ private long roleId = 1L;
private short accountType = 1;
private Long domainId = 1L;
@@ -69,7 +73,7 @@ public class CreateAccountCmdTest {
} catch (ServerApiException e) {
Assert.assertTrue("Received exception as the mock accountService createUserAccount returns null user", true);
}
- Mockito.verify(accountService, Mockito.times(1)).createUserAccount(null, "Test", null, null, null, null, null, accountType, domainId, null, null, null, null);
+ Mockito.verify(accountService, Mockito.times(1)).createUserAccount(null, "Test", null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
}
@Test
@@ -82,7 +86,7 @@ public class CreateAccountCmdTest {
Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
Assert.assertEquals("Empty passwords are not allowed", e.getMessage());
}
- Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, domainId, null, null, null, null);
+ Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
}
@Test
@@ -95,6 +99,6 @@ public class CreateAccountCmdTest {
Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
Assert.assertEquals("Empty passwords are not allowed", e.getMessage());
}
- Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, domainId, null, null, null, null);
+ Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
}
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java
----------------------------------------------------------------------
diff --git a/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java
index 63c4a03..65b9330 100644
--- a/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java
+++ b/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java
@@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.test;
import junit.framework.Assert;
import junit.framework.TestCase;
+import org.apache.cloudstack.acl.RoleService;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -49,21 +50,45 @@ public class UpdateCfgCmdTest extends TestCase {
}
@Test
+ public void testExecuteForEmptyCfgName() {
+ updateCfgCmd._configService = configService;
+
+ try {
+ updateCfgCmd.execute();
+ } catch (ServerApiException exception) {
+ Assert.assertEquals("Empty configuration name provided", exception.getDescription());
+ }
+ }
+
+ @Test
+ public void testExecuteForRestrictedCfg() {
+ updateCfgCmd._configService = configService;
+ updateCfgCmd.setCfgName(RoleService.EnableDynamicApiChecker.key());
+
+ try {
+ updateCfgCmd.execute();
+ } catch (ServerApiException exception) {
+ Assert.assertEquals("Restricted configuration update not allowed", exception.getDescription());
+ }
+ }
+
+ @Test
public void testExecuteForEmptyResult() {
updateCfgCmd._configService = configService;
+ updateCfgCmd.setCfgName("some.cfg");
try {
updateCfgCmd.execute();
} catch (ServerApiException exception) {
Assert.assertEquals("Failed to update config", exception.getDescription());
}
-
}
@Test
public void testExecuteForNullResult() {
updateCfgCmd._configService = configService;
+ updateCfgCmd.setCfgName("some.cfg");
try {
Mockito.when(configService.updateConfiguration(updateCfgCmd)).thenReturn(null);
@@ -88,6 +113,7 @@ public class UpdateCfgCmdTest extends TestCase {
Configuration cfg = Mockito.mock(Configuration.class);
updateCfgCmd._configService = configService;
updateCfgCmd._responseGenerator = responseGenerator;
+ updateCfgCmd.setCfgName("some.cfg");
try {
Mockito.when(configService.updateConfiguration(updateCfgCmd)).thenReturn(cfg);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/client/WEB-INF/classes/resources/messages.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties
index fbdedda..5e97907 100644
--- a/client/WEB-INF/classes/resources/messages.properties
+++ b/client/WEB-INF/classes/resources/messages.properties
@@ -1095,9 +1095,17 @@ label.retry.interval=Retry Interval
label.review=Review
label.revoke.project.invite=Revoke invitation
label.role=Role
+label.roles=Roles
+label.roletype=Role Type
+label.add.role=Add Role
+label.edit.role=Edit Role
+label.delete.role=Delete Role
+message.role.ordering.fail=Reordering of rule permissions aborted as the list has changed while you were making changes. Please try again.
label.root.certificate=Root certificate
label.root.disk.controller=Root disk controller
label.root.disk.offering=Root Disk Offering
+label.permission=Permission
+label.rule=Rule
label.rules=Rules
label.running.vms=Running VMs
label.s3.access_key=Access Key
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/client/pom.xml
----------------------------------------------------------------------
diff --git a/client/pom.xml b/client/pom.xml
index a1e4f60..9bd80b7 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -52,6 +52,11 @@
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
+ <artifactId>cloud-plugin-acl-dynamic-role-based</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-dedicated-resources</artifactId>
<version>${project.version}</version>
</dependency>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/debian/cloudstack-management.install
----------------------------------------------------------------------
diff --git a/debian/cloudstack-management.install b/debian/cloudstack-management.install
index 4e016df..c669382 100644
--- a/debian/cloudstack-management.install
+++ b/debian/cloudstack-management.install
@@ -18,7 +18,6 @@
/etc/cloudstack/management/catalina.policy
/etc/cloudstack/management/catalina.properties
/etc/cloudstack/management/logging.properties
-/etc/cloudstack/management/commands.properties
/etc/cloudstack/management/ehcache.xml
/etc/cloudstack/management/server-ssl.xml
/etc/cloudstack/management/server-nonssl.xml
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/debian/control
----------------------------------------------------------------------
diff --git a/debian/control b/debian/control
index 05058a8..6e7881b 100644
--- a/debian/control
+++ b/debian/control
@@ -15,7 +15,7 @@ Description: A common package which contains files which are shared by several C
Package: cloudstack-management
Architecture: all
-Depends: ${misc:Depends}, ${python:Depends}, cloudstack-common (= ${source:Version}), tomcat6, sudo, jsvc, python-mysql.connector, libmysql-java, augeas-tools, mysql-client, adduser, bzip2
+Depends: ${misc:Depends}, ${python:Depends}, cloudstack-common (= ${source:Version}), tomcat6 | tomcat7, sudo, jsvc, python-mysql.connector, libmysql-java, augeas-tools, mysql-client, adduser, bzip2
Conflicts: cloud-server, cloud-client, cloud-client-ui
Description: CloudStack server library
The CloudStack management server
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/developer/developer-prefill.sql
----------------------------------------------------------------------
diff --git a/developer/developer-prefill.sql b/developer/developer-prefill.sql
index f874118..0dbe180 100644
--- a/developer/developer-prefill.sql
+++ b/developer/developer-prefill.sql
@@ -106,6 +106,11 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
VALUES ('Advanced', 'DEFAULT', 'management-server',
'direct.agent.load.size', '1000');
+-- Enable dynamic RBAC by default for fresh deployments
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+ VALUES ('Advanced', 'DEFAULT', 'RoleService',
+ 'dynamic.apichecker.enabled', 'true');
+
-- Add developer configuration entry; allows management server to be run as a user other than "cloud"
INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
VALUES ('Advanced', 'DEFAULT', 'management-server',
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
----------------------------------------------------------------------
diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index 67ed2a7..a3d58fc 100644
--- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -113,6 +113,8 @@
<!--
DAOs with default configuration
-->
+ <bean id="roleDaoImpl" class="org.apache.cloudstack.acl.dao.RoleDaoImpl" />
+ <bean id="rolePermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.RolePermissionsDaoImpl" />
<bean id="accountDaoImpl" class="com.cloud.user.dao.AccountDaoImpl" />
<bean id="accountDetailsDaoImpl" class="com.cloud.user.AccountDetailsDaoImpl" />
<bean id="accountJoinDaoImpl" class="com.cloud.api.query.dao.AccountJoinDaoImpl" />
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/com/cloud/upgrade/dao/Upgrade481to490.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade481to490.java b/engine/schema/src/com/cloud/upgrade/dao/Upgrade481to490.java
index 9e52134..d1240ec 100644
--- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade481to490.java
+++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade481to490.java
@@ -17,12 +17,21 @@
package com.cloud.upgrade.dao;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.db.ScriptRunner;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;
+import org.apache.cloudstack.acl.RoleType;
import org.apache.log4j.Logger;
import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
public class Upgrade481to490 implements DbUpgrade {
final static Logger s_logger = Logger.getLogger(Upgrade481to490.class);
@@ -53,6 +62,71 @@ public class Upgrade481to490 implements DbUpgrade {
@Override
public void performDataMigration(Connection conn) {
+ setupRolesAndPermissionsForDynamicChecker(conn);
+ }
+
+ private void migrateAccountsToDefaultRoles(final Connection conn) {
+ try (final PreparedStatement selectStatement = conn.prepareStatement("SELECT `id`, `type` FROM `cloud`.`account`;");
+ final ResultSet selectResultSet = selectStatement.executeQuery()) {
+ while (selectResultSet.next()) {
+ final Long accountId = selectResultSet.getLong(1);
+ final Short accountType = selectResultSet.getShort(2);
+ final Long roleId = RoleType.getByAccountType(accountType).getId();
+ if (roleId < 1L || roleId > 4L) {
+ s_logger.warn("Skipping role ID migration due to invalid role_id resolved for account id=" + accountId);
+ continue;
+ }
+ try (final PreparedStatement updateStatement = conn.prepareStatement("UPDATE `cloud`.`account` SET account.role_id = ? WHERE account.id = ? ;")) {
+ updateStatement.setLong(1, roleId);
+ updateStatement.setLong(2, accountId);
+ updateStatement.executeUpdate();
+ } catch (SQLException e) {
+ s_logger.error("Failed to update cloud.account role_id for account id:" + accountId + " with exception: " + e.getMessage());
+ throw new CloudRuntimeException("Exception while updating cloud.account role_id", e);
+ }
+ }
+ } catch (SQLException e) {
+ throw new CloudRuntimeException("Exception while migrating existing account table's role_id column to a role based on account type", e);
+ }
+ s_logger.debug("Done migrating existing accounts to use one of default roles based on account type");
+ }
+
+ private void setupRolesAndPermissionsForDynamicChecker(final Connection conn) {
+ final String alterTableSql = "ALTER TABLE `cloud`.`account` " +
+ "ADD COLUMN `role_id` bigint(20) unsigned COMMENT 'role id for this account' AFTER `type`, " +
+ "ADD KEY `fk_account__role_id` (`role_id`), " +
+ "ADD CONSTRAINT `fk_account__role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`);";
+ try (final PreparedStatement pstmt = conn.prepareStatement(alterTableSql)) {
+ pstmt.executeUpdate();
+ } catch (SQLException e) {
+ if (e.getMessage().contains("role_id")) {
+ s_logger.warn("cloud.account table already has the role_id column, skipping altering table and migration of accounts");
+ return;
+ } else {
+ throw new CloudRuntimeException("Unable to create column quota_calculated in table cloud_usage.cloud_usage", e);
+ }
+ }
+
+ migrateAccountsToDefaultRoles(conn);
+
+ final Map<String, String> apiMap = PropertiesUtil.processConfigFile(new String[] { PropertiesUtil.getDefaultApiCommandsFileName() });
+ if (apiMap == null || apiMap.isEmpty()) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("The commands.properties file and default role permissions were not found. " +
+ "Assuming new installation, configuring default role-api mappings.");
+ }
+ String script = Script.findScript("", "db/create-default-role-api-mappings.sql");
+ if (script == null) {
+ s_logger.error("Unable to find default role-api mapping sql file, please configure api per role manually");
+ return;
+ }
+ try(final FileReader reader = new FileReader(new File(script))) {
+ ScriptRunner runner = new ScriptRunner(conn, false, true);
+ runner.runScript(reader);
+ } catch (SQLException | IOException e) {
+ s_logger.error("Unable to insert default api-role mappings from file: " + script + ". Please configure api per role manually, giving up!", e);
+ }
+ }
}
@Override
@@ -61,7 +135,6 @@ public class Upgrade481to490 implements DbUpgrade {
if (script == null) {
throw new CloudRuntimeException("Unable to find db/schema-481to490-cleanup.sql");
}
-
return new File[] {new File(script)};
}
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/com/cloud/user/AccountVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/AccountVO.java b/engine/schema/src/com/cloud/user/AccountVO.java
index 0f5a044..a504d2f 100644
--- a/engine/schema/src/com/cloud/user/AccountVO.java
+++ b/engine/schema/src/com/cloud/user/AccountVO.java
@@ -16,8 +16,8 @@
// under the License.
package com.cloud.user;
-import java.util.Date;
-import java.util.UUID;
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.acl.RoleType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -27,8 +27,8 @@ import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
-
-import com.cloud.utils.db.GenericDao;
+import java.util.Date;
+import java.util.UUID;
@Entity
@Table(name = "account")
@@ -44,6 +44,9 @@ public class AccountVO implements Account {
@Column(name = "type")
private short type = ACCOUNT_TYPE_NORMAL;
+ @Column(name = "role_id")
+ private Long roleId;
+
@Column(name = "domain_id")
private long domainId;
@@ -78,13 +81,21 @@ public class AccountVO implements Account {
uuid = UUID.randomUUID().toString();
}
- public AccountVO(String accountName, long domainId, String networkDomain, short type, String uuid) {
+ public AccountVO(final String accountName, final long domainId, final String networkDomain, final short type, final String uuid) {
this.accountName = accountName;
this.domainId = domainId;
this.networkDomain = networkDomain;
this.type = type;
- state = State.enabled;
+ this.state = State.enabled;
this.uuid = uuid;
+ this.roleId = RoleType.getRoleByAccountType(null, type);
+ }
+
+ public AccountVO(final String accountName, final long domainId, final String networkDomain, final short type, final Long roleId, final String uuid) {
+ this(accountName, domainId, networkDomain, type, uuid);
+ if (roleId != null) {
+ this.roleId = roleId;
+ }
}
public void setNeedsCleanup(boolean value) {
@@ -122,6 +133,14 @@ public class AccountVO implements Account {
this.type = type;
}
+ public Long getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(long roleId) {
+ this.roleId = roleId;
+ }
+
@Override
public long getDomainId() {
return domainId;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/com/cloud/user/dao/AccountDao.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/dao/AccountDao.java b/engine/schema/src/com/cloud/user/dao/AccountDao.java
index 4c7ce8e..374c9cc 100644
--- a/engine/schema/src/com/cloud/user/dao/AccountDao.java
+++ b/engine/schema/src/com/cloud/user/dao/AccountDao.java
@@ -41,6 +41,8 @@ public interface AccountDao extends GenericDao<AccountVO, Long> {
List<AccountVO> findActiveAccountsForDomain(Long domain);
+ List<AccountVO> findAccountsByRole(Long roleId);
+
void markForCleanup(long accountId);
List<AccountVO> listAccounts(String accountName, Long domainId, Filter filter);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/com/cloud/user/dao/AccountDaoImpl.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/dao/AccountDaoImpl.java b/engine/schema/src/com/cloud/user/dao/AccountDaoImpl.java
index bff6213..2789150 100644
--- a/engine/schema/src/com/cloud/user/dao/AccountDaoImpl.java
+++ b/engine/schema/src/com/cloud/user/dao/AccountDaoImpl.java
@@ -44,7 +44,7 @@ import com.cloud.utils.db.TransactionLegacy;
public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements AccountDao {
private static final Logger s_logger = Logger.getLogger(AccountDaoImpl.class);
private static final String FIND_USER_ACCOUNT_BY_API_KEY = "SELECT u.id, u.username, u.account_id, u.secret_key, u.state, "
- + "a.id, a.account_name, a.type, a.domain_id, a.state " + "FROM `cloud`.`user` u, `cloud`.`account` a "
+ + "a.id, a.account_name, a.type, a.role_id, a.domain_id, a.state " + "FROM `cloud`.`user` u, `cloud`.`account` a "
+ "WHERE u.account_id = a.id AND u.api_key = ? and u.removed IS NULL";
protected final SearchBuilder<AccountVO> AllFieldsSearch;
@@ -53,6 +53,7 @@ public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements A
protected final SearchBuilder<AccountVO> CleanupForRemovedAccountsSearch;
protected final SearchBuilder<AccountVO> CleanupForDisabledAccountsSearch;
protected final SearchBuilder<AccountVO> NonProjectAccountSearch;
+ protected final SearchBuilder<AccountVO> AccountByRoleSearch;
protected final GenericSearchBuilder<AccountVO, Long> AccountIdsSearch;
public AccountDaoImpl() {
@@ -96,6 +97,10 @@ public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements A
AccountIdsSearch.selectFields(AccountIdsSearch.entity().getId());
AccountIdsSearch.and("ids", AccountIdsSearch.entity().getDomainId(), Op.IN);
AccountIdsSearch.done();
+
+ AccountByRoleSearch = createSearchBuilder();
+ AccountByRoleSearch.and("roleId", AccountByRoleSearch.entity().getRoleId(), SearchCriteria.Op.EQ);
+ AccountByRoleSearch.done();
}
@Override
@@ -140,8 +145,9 @@ public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements A
AccountVO a = new AccountVO(rs.getLong(6));
a.setAccountName(rs.getString(7));
a.setType(rs.getShort(8));
- a.setDomainId(rs.getLong(9));
- a.setState(State.valueOf(rs.getString(10)));
+ a.setRoleId(rs.getLong(9));
+ a.setDomainId(rs.getLong(10));
+ a.setState(State.valueOf(rs.getString(11)));
userAcctPair = new Pair<User, Account>(u, a);
}
@@ -259,6 +265,13 @@ public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements A
}
@Override
+ public List<AccountVO> findAccountsByRole(Long roleId) {
+ SearchCriteria<AccountVO> sc = AccountByRoleSearch.create();
+ sc.setParameters("roleId", roleId);
+ return listBy(sc);
+ }
+
+ @Override
public void markForCleanup(long accountId) {
AccountVO account = findByIdIncludingRemoved(accountId);
if (!account.getNeedsCleanup()) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/RolePermissionVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/RolePermissionVO.java b/engine/schema/src/org/apache/cloudstack/acl/RolePermissionVO.java
new file mode 100644
index 0000000..a81cebb
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/RolePermissionVO.java
@@ -0,0 +1,120 @@
+// 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.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;
+
+ @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(final long roleId, final String rule, final Permission permission, final String description) {
+ this();
+ 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() {
+ return roleId;
+ }
+
+ 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;
+ }
+
+ public void setSortOrder(long sortOrder) {
+ this.sortOrder = sortOrder;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/RoleVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/RoleVO.java b/engine/schema/src/org/apache/cloudstack/acl/RoleVO.java
new file mode 100644
index 0000000..f3404ab
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/RoleVO.java
@@ -0,0 +1,106 @@
+// 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 com.cloud.utils.db.GenericDao;
+
+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.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "roles")
+public class RoleVO implements Role {
+ @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 = "role_type", nullable = false)
+ @Enumerated(value = EnumType.STRING)
+ private RoleType roleType = RoleType.User;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ private Date removed;
+
+ public RoleVO() {
+ this.uuid = UUID.randomUUID().toString();
+ }
+
+ public RoleVO(final String name, final RoleType roleType, final String description) {
+ this();
+ this.name = name;
+ this.roleType = roleType;
+ this.description = description;
+ }
+
+ public RoleVO(final long id, final String name, final RoleType roleType, final String description) {
+ this(name, roleType, description);
+ this.id = id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public RoleType getRoleType() {
+ return roleType;
+ }
+
+ public void setRoleType(RoleType roleType) {
+ this.roleType = roleType;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDao.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDao.java b/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDao.java
new file mode 100644
index 0000000..e53654d
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDao.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 com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.acl.RoleVO;
+
+import java.util.List;
+
+public interface RoleDao extends GenericDao<RoleVO, Long> {
+ List<RoleVO> findAllByName(String roleName);
+ List<RoleVO> findAllByRoleType(RoleType type);
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDaoImpl.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDaoImpl.java b/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDaoImpl.java
new file mode 100644
index 0000000..c7eb496
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/dao/RoleDaoImpl.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 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;
+
+import javax.ejb.Local;
+import java.util.List;
+
+@Component
+@Local(value = {RoleDao.class})
+public class RoleDaoImpl extends GenericDaoBase<RoleVO, Long> implements RoleDao {
+ private final SearchBuilder<RoleVO> RoleByNameSearch;
+ private final SearchBuilder<RoleVO> RoleByTypeSearch;
+
+ public RoleDaoImpl() {
+ super();
+
+ RoleByNameSearch = createSearchBuilder();
+ RoleByNameSearch.and("roleName", RoleByNameSearch.entity().getName(), SearchCriteria.Op.LIKE);
+ RoleByNameSearch.done();
+
+ RoleByTypeSearch = createSearchBuilder();
+ RoleByTypeSearch.and("roleType", RoleByTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ);
+ RoleByTypeSearch.done();
+ }
+
+ @Override
+ public List<RoleVO> findAllByName(final String roleName) {
+ SearchCriteria<RoleVO> sc = RoleByNameSearch.create();
+ sc.setParameters("roleName", "%" + roleName + "%");
+ return listBy(sc);
+ }
+
+ @Override
+ public List<RoleVO> findAllByRoleType(final RoleType type) {
+ SearchCriteria<RoleVO> sc = RoleByTypeSearch.create();
+ sc.setParameters("roleType", type);
+ return listBy(sc);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDao.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDao.java b/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDao.java
new file mode 100644
index 0000000..3754491
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDao.java
@@ -0,0 +1,49 @@
+// 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 com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RolePermission;
+import org.apache.cloudstack.acl.RolePermissionVO;
+
+import java.util.List;
+
+public interface RolePermissionsDao extends GenericDao<RolePermissionVO, Long> {
+ /**
+ * Adds a new role permission at the end of the list of role permissions
+ * @param item the new role permission
+ * @return returns persisted role permission
+ */
+ RolePermissionVO persist(final RolePermissionVO item);
+
+ /**
+ * Moves an existing role permission under a given parent role permission
+ * @param role the existing role
+ * @param newOrder the new role permissions order
+ * @return returns true on success
+ */
+ boolean update(final Role role, final List<RolePermission> newOrder);
+
+ /**
+ * Returns ordered linked-list of role permission for a given role
+ * @param roleId the ID of the role
+ * @return returns list of role permissions
+ */
+ List<RolePermissionVO> findAllByRoleIdSorted(Long roleId);
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java b/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java
new file mode 100644
index 0000000..960b652
--- /dev/null
+++ b/engine/schema/src/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java
@@ -0,0 +1,166 @@
+// 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 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;
+import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RolePermission;
+import org.apache.cloudstack.acl.RolePermissionVO;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.ejb.Local;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Component
+@Local(value = {RolePermissionsDao.class})
+public class RolePermissionsDaoImpl extends GenericDaoBase<RolePermissionVO, Long> implements RolePermissionsDao {
+ protected static final Logger LOGGER = Logger.getLogger(RolePermissionsDaoImpl.class);
+
+ private final SearchBuilder<RolePermissionVO> RolePermissionsSearch;
+ private Attribute sortOrderAttribute;
+
+ public RolePermissionsDaoImpl() {
+ super();
+
+ RolePermissionsSearch = createSearchBuilder();
+ RolePermissionsSearch.and("uuid", RolePermissionsSearch.entity().getUuid(), SearchCriteria.Op.EQ);
+ RolePermissionsSearch.and("roleId", RolePermissionsSearch.entity().getRoleId(), SearchCriteria.Op.EQ);
+ RolePermissionsSearch.and("sortOrder", RolePermissionsSearch.entity().getSortOrder(), SearchCriteria.Op.EQ);
+ RolePermissionsSearch.done();
+
+ sortOrderAttribute = _allAttributes.get("sortOrder");
+
+ assert (sortOrderAttribute != null) : "Couldn't find one of these attributes";
+ }
+
+ private boolean updateSortOrder(final RolePermissionVO permissionBeingMoved, final RolePermissionVO parentPermission) {
+ if (parentPermission != null && permissionBeingMoved.getId() == parentPermission.getId()) {
+ return true;
+ }
+ final List<RolePermissionVO> newOrderedPermissionsList = new ArrayList<>();
+ // Null parent implies item needs to move to the top
+ if (parentPermission == null) {
+ newOrderedPermissionsList.add(permissionBeingMoved);
+ }
+ for (final RolePermissionVO permission : findAllByRoleIdSorted(permissionBeingMoved.getRoleId())) {
+ if (permission.getId() == permissionBeingMoved.getId()) {
+ continue;
+ }
+ newOrderedPermissionsList.add(permission);
+ if (parentPermission != null && permission.getId() == parentPermission.getId()) {
+ newOrderedPermissionsList.add(permissionBeingMoved);
+ }
+ }
+ long sortOrder = 0L;
+ for (final RolePermissionVO permission : newOrderedPermissionsList) {
+ permission.setSortOrder(sortOrder++);
+ if (!update(permission.getId(), permission)) {
+ LOGGER.warn("Failed to update item's sort order with id:" + permission.getId() + " while moving permission with id:" + permissionBeingMoved.getId() + " to a new position");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public RolePermissionVO persist(final RolePermissionVO item) {
+ item.setSortOrder(0);
+ final List<RolePermissionVO> permissionsList = findAllByRoleIdSorted(item.getRoleId());
+ if (permissionsList != null && permissionsList.size() > 0) {
+ RolePermission lastRule = permissionsList.get(permissionsList.size() - 1);
+ item.setSortOrder(lastRule.getSortOrder() + 1);
+ }
+ return super.persist(item);
+ }
+
+ @Override
+ public boolean update(final Role role, final List<RolePermission> newOrder) {
+ if (role == null || newOrder == null || newOrder.isEmpty()) {
+ return false;
+ }
+ return Transaction.execute(new TransactionCallback<Boolean>() {
+ @Override
+ public Boolean doInTransaction(TransactionStatus status) {
+ final String failMessage = "The role's rule permissions list has changed while you were making updates, aborted re-ordering of rules. Please try again.";
+ final List<RolePermissionVO> currentOrder = findAllByRoleIdSorted(role.getId());
+ if (role.getId() < 1L || newOrder.size() != currentOrder.size()) {
+ throw new CloudRuntimeException(failMessage);
+ }
+ final Set<Long> newOrderSet = new HashSet<>();
+ for (final RolePermission permission : newOrder) {
+ if (permission == null) {
+ continue;
+ }
+ newOrderSet.add(permission.getId());
+ }
+ final Set<Long> currentOrderSet = new HashSet<>();
+ for (final RolePermission permission : currentOrder) {
+ currentOrderSet.add(permission.getId());
+ }
+ if (!newOrderSet.equals(currentOrderSet)) {
+ throw new CloudRuntimeException(failMessage);
+ }
+ long sortOrder = 0L;
+ for (RolePermission rolePermission : newOrder) {
+ final SearchCriteria<RolePermissionVO> sc = RolePermissionsSearch.create();
+ sc.setParameters("uuid", rolePermission.getUuid());
+ sc.setParameters("roleId", role.getId());
+ sc.setParameters("sortOrder", rolePermission.getSortOrder());
+
+ final UpdateBuilder ub = getUpdateBuilder(rolePermission);
+ ub.set(rolePermission, sortOrderAttribute, sortOrder);
+ final int result = update(ub, sc, null);
+ if (result < 1) {
+ throw new CloudRuntimeException(failMessage);
+ }
+ sortOrder++;
+ }
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public List<RolePermissionVO> findAllByRoleIdSorted(final Long roleId) {
+ final SearchCriteria<RolePermissionVO> sc = RolePermissionsSearch.create();
+ if (roleId != null && roleId > 0L) {
+ sc.setParameters("roleId", roleId);
+ }
+ final Filter searchBySorted = new Filter(RolePermissionVO.class, "sortOrder", true, null, null);
+ searchBySorted.addOrderBy(RolePermissionVO.class, "id", true);
+ final List<RolePermissionVO> rolePermissionList = listBy(sc, searchBySorted);
+ if (rolePermissionList == null) {
+ return Collections.emptyList();
+ }
+ return rolePermissionList;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/dynamic-role-based/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/acl/dynamic-role-based/pom.xml b/plugins/acl/dynamic-role-based/pom.xml
new file mode 100644
index 0000000..96d7e53
--- /dev/null
+++ b/plugins/acl/dynamic-role-based/pom.xml
@@ -0,0 +1,32 @@
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>cloud-plugin-acl-dynamic-role-based</artifactId>
+ <name>Apache CloudStack Plugin - ACL Dynamic Role Based</name>
+ <parent>
+ <groupId>org.apache.cloudstack</groupId>
+ <artifactId>cloudstack-plugins</artifactId>
+ <version>4.9.0-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+</project>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/module.properties
----------------------------------------------------------------------
diff --git a/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/module.properties b/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/module.properties
new file mode 100644
index 0000000..dda9998
--- /dev/null
+++ b/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-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-dynamic-role-based
+parent=api
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/spring-acl-dynamic-role-based-context.xml
----------------------------------------------------------------------
diff --git a/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/spring-acl-dynamic-role-based-context.xml b/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/spring-acl-dynamic-role-based-context.xml
new file mode 100644
index 0000000..9ffe465
--- /dev/null
+++ b/plugins/acl/dynamic-role-based/resources/META-INF/cloudstack/acl-dynamic-role-based/spring-acl-dynamic-role-based-context.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ >
+
+ <bean id="DynamicRoleBasedAPIAccessChecker" class="org.apache.cloudstack.acl.DynamicRoleBasedAPIAccessChecker" >
+ <property name="services" value="#{apiCommandsRegistry.registered}" />
+ </bean>
+</beans>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/dynamic-role-based/src/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java
----------------------------------------------------------------------
diff --git a/plugins/acl/dynamic-role-based/src/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java
new file mode 100644
index 0000000..754d4cc
--- /dev/null
+++ b/plugins/acl/dynamic-role-based/src/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java
@@ -0,0 +1,144 @@
+// 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 com.cloud.exception.PermissionDeniedException;
+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;
+import com.google.common.base.Strings;
+import org.apache.cloudstack.api.APICommand;
+
+import javax.ejb.Local;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Local(value = APIChecker.class)
+public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements APIChecker {
+
+ @Inject
+ private AccountService accountService;
+ @Inject
+ private RoleService roleService;
+
+ private List<PluggableService> services;
+ private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<>();
+
+ protected DynamicRoleBasedAPIAccessChecker() {
+ super();
+ for (RoleType roleType : RoleType.values()) {
+ annotationRoleBasedApisMap.put(roleType, new HashSet<String>());
+ }
+ }
+
+ private void denyApiAccess(final String commandName) throws PermissionDeniedException {
+ throw new PermissionDeniedException("The API does not exist or is blacklisted for the account's role. " +
+ "The account with is not allowed to request the api: " + commandName);
+ }
+
+ public boolean isDisabled() {
+ return !roleService.isEnabled();
+ }
+
+ @Override
+ public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
+ if (isDisabled()) {
+ return true;
+ }
+ Account account = accountService.getAccount(user.getAccountId());
+ if (account == null) {
+ throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null");
+ }
+
+ final Role accountRole = roleService.findRole(account.getRoleId());
+ if (accountRole == null || accountRole.getId() < 1L) {
+ denyApiAccess(commandName);
+ }
+
+ // Allow all APIs for root admins
+ if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
+ return true;
+ }
+
+ // 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())) {
+ return true;
+ } else {
+ denyApiAccess(commandName);
+ }
+ }
+ }
+
+ // Check annotations
+ if (annotationRoleBasedApisMap.get(accountRole.getRoleType()) != null
+ && annotationRoleBasedApisMap.get(accountRole.getRoleType()).contains(commandName)) {
+ return true;
+ }
+
+ // Default deny all
+ denyApiAccess(commandName);
+ return false;
+ }
+
+ public void addApiToRoleBasedAnnotationsMap(final RoleType roleType, final String commandName) {
+ if (roleType == null || Strings.isNullOrEmpty(commandName)) {
+ return;
+ }
+ final Set<String> commands = annotationRoleBasedApisMap.get(roleType);
+ if (commands != null && !commands.contains(commandName)) {
+ commands.add(commandName);
+ }
+ }
+
+ @Override
+ public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+ super.configure(name, params);
+ return true;
+ }
+
+ @Override
+ public boolean start() {
+ for (PluggableService service : services) {
+ for (Class<?> clz : service.getCommands()) {
+ APICommand command = clz.getAnnotation(APICommand.class);
+ for (RoleType role : command.authorized()) {
+ addApiToRoleBasedAnnotationsMap(role, command.name());
+ }
+ }
+ }
+ return super.start();
+ }
+
+ public List<PluggableService> getServices() {
+ return services;
+ }
+
+ @Inject
+ public void setServices(List<PluggableService> services) {
+ this.services = services;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/dynamic-role-based/test/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
----------------------------------------------------------------------
diff --git a/plugins/acl/dynamic-role-based/test/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java b/plugins/acl/dynamic-role-based/test/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
new file mode 100644
index 0000000..12ebbe5
--- /dev/null
+++ b/plugins/acl/dynamic-role-based/test/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
@@ -0,0 +1,164 @@
+// 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 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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
+
+ @Mock
+ private AccountService accountService;
+ @Mock
+ private RoleService roleService;
+
+ private DynamicRoleBasedAPIAccessChecker apiAccessChecker;
+
+ private User getTestUser() {
+ return new UserVO(12L, "some user", "password", "firstName", "lastName",
+ "email@gmail.com", "GMT", "uuid", User.Source.UNKNOWN);
+ }
+
+ private Account getTestAccount() {
+ return new AccountVO("some name", 1L, "network-domain", (short)0, "some-uuid");
+ }
+
+ private Role getTestRole() {
+ return new RoleVO(4L, "SomeRole", RoleType.User, "some description");
+ }
+
+ private void setupMockField(final Object obj, final String fieldName, final Object mock) throws NoSuchFieldException, IllegalAccessException {
+ Field roleDaoField = DynamicRoleBasedAPIAccessChecker.class.getDeclaredField(fieldName);
+ roleDaoField.setAccessible(true);
+ roleDaoField.set(obj, mock);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws NoSuchFieldException, IllegalAccessException {
+ apiAccessChecker = Mockito.spy(new DynamicRoleBasedAPIAccessChecker());
+ setupMockField(apiAccessChecker, "accountService", accountService);
+ setupMockField(apiAccessChecker, "roleService", roleService);
+
+ Mockito.when(accountService.getAccount(Mockito.anyLong())).thenReturn(getTestAccount());
+ Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn((RoleVO) getTestRole());
+
+ // Enabled plugin
+ Mockito.doReturn(false).when(apiAccessChecker).isDisabled();
+ Mockito.doCallRealMethod().when(apiAccessChecker).checkAccess(Mockito.any(User.class), Mockito.anyString());
+ }
+
+ @Test
+ public void testInvalidAccountCheckAccess() {
+ Mockito.when(accountService.getAccount(Mockito.anyLong())).thenReturn(null);
+ try {
+ apiAccessChecker.checkAccess(getTestUser(), "someApi");
+ fail("Exception was expected");
+ } catch (PermissionDeniedException ignored) {
+ }
+ }
+
+ @Test
+ public void testInvalidAccountRoleCheckAccess() {
+ Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(null);
+ try {
+ apiAccessChecker.checkAccess(getTestUser(), "someApi");
+ fail("Exception was expected");
+ } catch (PermissionDeniedException ignored) {
+ }
+ }
+
+ @Test
+ public void testDefaultRootAdminAccess() {
+ Mockito.when(accountService.getAccount(Mockito.anyLong())).thenReturn(new AccountVO("root admin", 1L, null, (short)1, "some-uuid"));
+ Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO(1L, "SomeRole", RoleType.Admin, "default root admin role"));
+ assertTrue(apiAccessChecker.checkAccess(getTestUser(), "anyApi"));
+ }
+
+ @Test
+ public void testInvalidRolePermissionsCheckAccess() {
+ Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.<RolePermission>emptyList());
+ try {
+ apiAccessChecker.checkAccess(getTestUser(), "someApi");
+ fail("Exception was expected");
+ } catch (PermissionDeniedException ignored) {
+ }
+ }
+
+ @Test
+ public void testValidAllowRolePermissionApiCheckAccess() {
+ final String allowedApiName = "someAllowedApi";
+ final RolePermission permission = new RolePermissionVO(1L, allowedApiName, RolePermission.Permission.ALLOW, null);
+ Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ assertTrue(apiAccessChecker.checkAccess(getTestUser(), allowedApiName));
+ }
+
+ @Test
+ public void testValidAllowRolePermissionWildcardCheckAccess() {
+ final String allowedApiName = "someAllowedApi";
+ final RolePermission permission = new RolePermissionVO(1L, "some*", RolePermission.Permission.ALLOW, null);
+ Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ assertTrue(apiAccessChecker.checkAccess(getTestUser(), allowedApiName));
+ }
+
+ @Test
+ public void testValidDenyRolePermissionApiCheckAccess() {
+ final String denyApiName = "someDeniedApi";
+ final RolePermission permission = new RolePermissionVO(1L, denyApiName, RolePermission.Permission.DENY, null);
+ Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ try {
+ apiAccessChecker.checkAccess(getTestUser(), denyApiName);
+ fail("Exception was expected");
+ } catch (PermissionDeniedException ignored) {
+ }
+ }
+
+ @Test
+ public void testValidDenyRolePermissionWildcardCheckAccess() {
+ final String denyApiName = "someDenyApi";
+ final RolePermission permission = new RolePermissionVO(1L, "*Deny*", RolePermission.Permission.DENY, null);
+ Mockito.when(roleService.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ try {
+ apiAccessChecker.checkAccess(getTestUser(), denyApiName);
+ fail("Exception was expected");
+ } catch (PermissionDeniedException ignored) {
+ }
+ }
+
+ @Test
+ public void testAnnotationFallbackCheckAccess() {
+ final String allowedApiName = "someApiWithAnnotations";
+ apiAccessChecker.addApiToRoleBasedAnnotationsMap(getTestRole().getRoleType(), allowedApiName);
+ assertTrue(apiAccessChecker.checkAccess(getTestUser(), allowedApiName));
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java
----------------------------------------------------------------------
diff --git a/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java
index 9221ea4..fc78268 100644
--- a/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java
+++ b/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java
@@ -41,18 +41,20 @@ import com.cloud.utils.component.PluggableService;
// based on the account type, access is granted
public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIChecker {
- protected static final Logger s_logger = Logger.getLogger(StaticRoleBasedAPIAccessChecker.class);
+ protected static final Logger LOGGER = Logger.getLogger(StaticRoleBasedAPIAccessChecker.class);
- Set<String> commandPropertyFiles = new HashSet<String>();
- Set<String> commandsPropertiesOverrides = new HashSet<String>();
- Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
- Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
+ private Set<String> commandPropertyFiles = new HashSet<String>();
+ private Set<String> commandsPropertiesOverrides = new HashSet<String>();
+ private Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
+ private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
+ private List<PluggableService> services;
- List<PluggableService> _services;
@Inject
- AccountService _accountService;
+ private AccountService accountService;
+ @Inject
+ private RoleService roleService;
- protected StaticRoleBasedAPIAccessChecker() {
+ public StaticRoleBasedAPIAccessChecker() {
super();
for (RoleType roleType : RoleType.values()) {
commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet<String>());
@@ -60,23 +62,31 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
}
}
+ public boolean isDisabled() {
+ return roleService.isEnabled();
+ }
+
@Override
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
- Account account = _accountService.getAccount(user.getAccountId());
+ if (isDisabled()) {
+ return true;
+ }
+
+ Account account = accountService.getAccount(user.getAccountId());
if (account == null) {
throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null");
}
- RoleType roleType = _accountService.getRoleType(account);
+ RoleType roleType = accountService.getRoleType(account);
boolean isAllowed =
commandsPropertiesOverrides.contains(commandName) ? commandsPropertiesRoleBasedApisMap.get(roleType).contains(commandName) : annotationRoleBasedApisMap.get(
roleType).contains(commandName);
- if (!isAllowed) {
- throw new PermissionDeniedException("The API does not exist or is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " +
- commandName);
+ if (isAllowed) {
+ return true;
}
- return isAllowed;
+
+ throw new PermissionDeniedException("The API does not exist or is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " + commandName);
}
@Override
@@ -91,7 +101,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
@Override
public boolean start() {
- for (PluggableService service : _services) {
+ for (PluggableService service : services) {
for (Class<?> clz : service.getCommands()) {
APICommand command = clz.getAnnotation(APICommand.class);
for (RoleType role : command.authorized()) {
@@ -112,22 +122,22 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
try {
short cmdPermissions = Short.parseShort(roleMask);
for (RoleType roleType : RoleType.values()) {
- if ((cmdPermissions & roleType.getValue()) != 0)
+ if ((cmdPermissions & roleType.getMask()) != 0)
commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName);
}
} catch (NumberFormatException nfe) {
- s_logger.info("Malformed key=value pair for entry: " + entry.toString());
+ LOGGER.info("Malformed key=value pair for entry: " + entry.toString());
}
}
}
public List<PluggableService> getServices() {
- return _services;
+ return services;
}
@Inject
public void setServices(List<PluggableService> services) {
- this._services = services;
+ this.services = services;
}
public Set<String> getCommandPropertyFiles() {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/api/discovery/src/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java
----------------------------------------------------------------------
diff --git a/plugins/api/discovery/src/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java b/plugins/api/discovery/src/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java
index be0d5d5..66d8c5d 100644
--- a/plugins/api/discovery/src/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java
+++ b/plugins/api/discovery/src/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java
@@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.user.discovery;
import javax.inject.Inject;
+import org.apache.cloudstack.acl.RoleType;
import org.apache.log4j.Logger;
import org.apache.cloudstack.api.APICommand;
@@ -38,7 +39,7 @@ import com.cloud.user.User;
description = "lists all available apis on the server, provided by the Api Discovery plugin",
since = "4.1.0",
requestHasSensitiveInfo = false,
- responseHasSensitiveInfo = false)
+ responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListApisCmd extends BaseCmd {
public static final Logger s_logger = Logger.getLogger(ListApisCmd.class.getName());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java
index 2451b56..aca7565 100644
--- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java
+++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java
@@ -37,12 +37,13 @@ import com.cloud.user.dao.UserDao;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.QueryBuilder;
import com.cloud.utils.db.SearchCriteria;
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.VirtualMachineProfile;
-import com.google.gson.Gson;
-import org.apache.cloudstack.api.AddBaremetalRctCmd;
-import org.apache.cloudstack.api.DeleteBaremetalRctCmd;
-import org.apache.cloudstack.api.ListBaremetalRctCmd;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachineProfile;
+import com.google.gson.Gson;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.AddBaremetalRctCmd;
+import org.apache.cloudstack.api.DeleteBaremetalRctCmd;
+import org.apache.cloudstack.api.ListBaremetalRctCmd;
import org.apache.cloudstack.utils.baremetal.BaremetalUtils;
import org.springframework.web.client.RestTemplate;
@@ -244,12 +245,14 @@ public class BaremetalVlanManagerImpl extends ManagerBase implements BaremetalVl
acnt = new AccountVO();
acnt.setAccountName(BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME);
- acnt.setUuid(UUID.randomUUID().toString());
- acnt.setState(Account.State.enabled);
- acnt.setDomainId(1);
- acnt = acntDao.persist(acnt);
-
- UserVO user = new UserVO();
+ acnt.setUuid(UUID.randomUUID().toString());
+ acnt.setState(Account.State.enabled);
+ acnt.setDomainId(1);
+ acnt.setType(RoleType.User.getAccountType());
+ acnt.setRoleId(RoleType.User.getId());
+ acnt = acntDao.persist(acnt);
+
+ UserVO user = new UserVO();
user.setState(Account.State.enabled);
user.setUuid(UUID.randomUUID().toString());
user.setAccountId(acnt.getAccountId());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
----------------------------------------------------------------------
diff --git a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index b5da604..e56766a 100644
--- a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@ -134,14 +134,14 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
- public UserAccount createUserAccount(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, short arg7, Long arg8, String arg9,
+ public UserAccount createUserAccount(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, short arg7, Long roleId, Long arg8, String arg9,
Map<String, String> arg10, String arg11, String arg12) {
// TODO Auto-generated method stub
return null;
}
@Override
- public UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType,
+ public UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType, Long roleId,
Long domainId, String networkDomain, Map<String, String> details, String accountUUID, String userUUID, User.Source source) {
// TODO Auto-generated method stub
return null;
@@ -392,8 +392,8 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
- public Account createAccount(String accountName, short accountType, Long domainId, String networkDomain, Map<String, String> details, String uuid) {
- final AccountVO account = new AccountVO(accountName, domainId, networkDomain, accountType, uuid);
+ public Account createAccount(String accountName, short accountType, Long roleId, Long domainId, String networkDomain, Map<String, String> details, String uuid) {
+ final AccountVO account = new AccountVO(accountName, domainId, networkDomain, accountType, roleId, uuid);
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/plugins/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/pom.xml b/plugins/pom.xml
index bafe299..1a25819 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -50,6 +50,7 @@
<module>api/solidfire-intg-test</module>
<module>api/discovery</module>
<module>acl/static-role-based</module>
+ <module>acl/dynamic-role-based</module>
<module>affinity-group-processors/host-anti-affinity</module>
<module>affinity-group-processors/explicit-dedication</module>
<module>deployment-planners/user-concentrated-pod</module>