diff --git a/.github/workflows/test-permission.yaml b/.github/workflows/test-permission.yaml new file mode 100644 index 0000000000..c6fbd5699d --- /dev/null +++ b/.github/workflows/test-permission.yaml @@ -0,0 +1,208 @@ +name: Test Permissions + +on: + workflow_dispatch: + pull_request: + +env: + TERM: dumb + JAVA_VERSION: '8' + JAVA_VENDOR: 'temurin' + +jobs: + integration-test-permission-cassandra-3-0: + name: Cassandra 3.0 Permission Integration Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Start Cassandra with authentication enabled + run: | + docker run -d --name cassandra \ + -p 9042:9042 \ + -e CASSANDRA_PASSWORD_SEEDER=yes \ + -e CASSANDRA_PASSWORD=cassandra \ + -e CASSANDRA_AUTHENTICATOR=PasswordAuthenticator \ + -e CASSANDRA_AUTHORIZER=CassandraAuthorizer \ + bitnami/cassandra:3.0 + + - name: Wait for Cassandra to be ready + run: sleep 60 + + - name: Execute Gradle 'integrationTestCassandraPermission' task + run: ./gradlew integrationTestCassandraPermission + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: cassandra_3.0_permission_integration_test_reports + path: core/build/reports/tests/integrationTestCassandraPermission + + integration-test-permission-cassandra-3-11: + name: Cassandra 3.11 Permission Integration Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Start Cassandra with authentication enabled + run: | + docker run -d --name cassandra \ + -p 9042:9042 \ + -e CASSANDRA_PASSWORD_SEEDER=yes \ + -e CASSANDRA_PASSWORD=cassandra \ + -e CASSANDRA_AUTHENTICATOR=PasswordAuthenticator \ + -e CASSANDRA_AUTHORIZER=CassandraAuthorizer \ + bitnami/cassandra:3.11 + + - name: Wait for Cassandra to be ready + run: sleep 60 + + - name: Execute Gradle 'integrationTestCassandraPermission' task + run: ./gradlew integrationTestCassandraPermission + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: cassandra_3.11_permission_integration_test_reports + path: core/build/reports/tests/integrationTestCassandraPermission + + integration-test-permission-dynamo: + name: DynamoDB Permission Integration Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Execute Gradle 'integrationTestDynamoPermission' task + run: ./gradlew integrationTestDynamoPermission + env: + DYNAMO_ACCESS_KEY_ID: ${{ secrets.DYNAMO_ACCESS_KEY }} + DYNAMO_SECRET_ACCESS_KEY: ${{ secrets.DYNAMO_SECRET_ACCESS_KEY }} + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: dynamo_permission_integration_test_reports + path: core/build/reports/tests/integrationTestDynamoPermission + + integration-test-permission-jdbc-mysql-5-7: + name: MySQL 5.7 Permission Integration Test + runs-on: ubuntu-latest + + steps: + - name: Run MySQL 5.7 + run: | + docker run -e MYSQL_ROOT_PASSWORD=mysql -p 3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin + + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Execute Gradle 'integrationTestJdbcPermission' task + run: ./gradlew integrationTestJdbcPermission + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: mysql_5.7_permission_integration_test_reports + path: core/build/reports/tests/integrationTestJdbcPermission + + integration-test-permission-jdbc-mysql-8-0: + name: MySQL 8.0 Permission Integration Test + runs-on: ubuntu-latest + + steps: + - name: Run MySQL 8.0 + run: | + docker run -e MYSQL_ROOT_PASSWORD=mysql -p 3306:3306 -d mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin + + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Execute Gradle 'integrationTestJdbcPermission' task + run: ./gradlew integrationTestJdbcPermission + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: mysql_8.0_permission_integration_test_reports + path: core/build/reports/tests/integrationTestJdbcPermission + + integration-test-permission-jdbc-mysql-8-4: + name: MySQL 8.4 Permission Integration Test + runs-on: ubuntu-latest + + steps: + - name: Run MySQL 8.4 + run: | + docker run -e MYSQL_ROOT_PASSWORD=mysql -p 3306:3306 -d mysql:8.4 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin + + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Execute Gradle 'integrationTestJdbcPermission' task + run: ./gradlew integrationTestJdbcPermission + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: mysql_8.4_permission_integration_test_reports + path: core/build/reports/tests/integrationTestJdbcPermission \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index f0ef34c045..a4b9d4d81a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -12,6 +12,15 @@ sourceSets { compileClasspath += main.output + test.output runtimeClasspath += main.output + test.output srcDir file('src/integration-test/java') + exclude '**/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java' } resources.srcDir file('src/integration-test/resources') } @@ -22,6 +31,9 @@ sourceSets { srcDir file('src/integration-test/java') include '**/com/scalar/db/common/*.java' include '**/com/scalar/db/storage/cassandra/*.java' + exclude '**/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java' } resources.srcDir file('src/integration-test/resources') } @@ -42,6 +54,9 @@ sourceSets { srcDir file('src/integration-test/java') include '**/com/scalar/db/common/*.java' include '**/com/scalar/db/storage/dynamo/*.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java' } resources.srcDir file('src/integration-test/resources') } @@ -53,6 +68,9 @@ sourceSets { include '**/com/scalar/db/common/*.java' include '**/com/scalar/db/storage/jdbc/*.java' include '**/com/scalar/db/transaction/jdbc/*.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java' + exclude '**/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java' } resources.srcDir file('src/integration-test/resources') } @@ -67,6 +85,49 @@ sourceSets { } resources.srcDir file('src/integration-test/resources') } + integrationTestCassandraPermission { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + include '**/com/scalar/db/common/*.java' + include '**/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java' + include '**/com/scalar/db/storage/cassandra/CassandraAdminTestUtils.java' + include '**/com/scalar/db/storage/cassandra/CassandraEnv.java' + include '**/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java' + include '**/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java' + } + resources.srcDir file('src/integration-test/resources') + } + integrationTestDynamoPermission { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + include '**/com/scalar/db/common/*.java' + include '**/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java' + include '**/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java' + include '**/com/scalar/db/storage/dynamo/DynamoEnv.java' + include '**/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java' + include '**/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java' + } + resources.srcDir file('src/integration-test/resources') + } + integrationTestJdbcPermission { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + include '**/com/scalar/db/common/*.java' + include '**/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java' + include '**/com/scalar/db/storage/jdbc/JdbcAdminTestUtils.java' + include '**/com/scalar/db/storage/jdbc/JdbcTestUtils.java' + include '**/com/scalar/db/storage/jdbc/JdbcEnv.java' + include '**/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java' + include '**/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java' + } + resources.srcDir file('src/integration-test/resources') + } } configurations { @@ -88,6 +149,15 @@ configurations { integrationTestMultiStorageImplementation.extendsFrom testImplementation integrationTestMultiStorageRuntimeOnly.extendsFrom testRuntimeOnly integrationTestMultiStorageCompileOnly.extendsFrom testCompileOnly + integrationTestCassandraPermissionImplementation.extendsFrom testImplementation + integrationTestCassandraPermissionRuntimeOnly.extendsFrom testRuntimeOnly + integrationTestCassandraPermissionCompileOnly.extendsFrom testCompileOnly + integrationTestDynamoPermissionImplementation.extendsFrom testImplementation + integrationTestDynamoPermissionRuntimeOnly.extendsFrom testRuntimeOnly + integrationTestDynamoPermissionCompileOnly.extendsFrom testCompileOnly + integrationTestJdbcPermissionImplementation.extendsFrom testImplementation + integrationTestJdbcPermissionRuntimeOnly.extendsFrom testRuntimeOnly + integrationTestJdbcPermissionCompileOnly.extendsFrom testCompileOnly } dependencies { @@ -100,6 +170,8 @@ dependencies { implementation platform("software.amazon.awssdk:bom:${awssdkVersion}") implementation 'software.amazon.awssdk:applicationautoscaling' implementation 'software.amazon.awssdk:dynamodb' + implementation 'software.amazon.awssdk:iam' + implementation 'software.amazon.awssdk:iam-policy-builder' implementation "org.apache.commons:commons-dbcp2:${commonsDbcp2Version}" implementation "com.mysql:mysql-connector-j:${mysqlDriverVersion}" implementation "org.postgresql:postgresql:${postgresqlDriverVersion}" @@ -200,6 +272,39 @@ task integrationTestMultiStorage(type: Test) { } } +task integrationTestCassandraPermission(type: Test) { + description = 'Runs the integration tests for Cassandra permissions.' + group = 'verification' + testClassesDirs = sourceSets.integrationTestCassandraPermission.output.classesDirs + classpath = sourceSets.integrationTestCassandraPermission.runtimeClasspath + outputs.upToDateWhen { false } // ensures integration tests are run every time when called + options { + systemProperties(System.getProperties().findAll { it.key.toString().startsWith("scalardb") }) + } +} + +task integrationTestDynamoPermission(type: Test) { + description = 'Runs the integration tests for DynamoDB permissions.' + group = 'verification' + testClassesDirs = sourceSets.integrationTestDynamoPermission.output.classesDirs + classpath = sourceSets.integrationTestDynamoPermission.runtimeClasspath + outputs.upToDateWhen { false } // ensures integration tests are run every time when called + options { + systemProperties(System.getProperties().findAll { it.key.toString().startsWith("scalardb") }) + } +} + +task integrationTestJdbcPermission(type: Test) { + description = 'Runs the integration tests for JDBC permissions.' + group = 'verification' + testClassesDirs = sourceSets.integrationTestJdbcPermission.output.classesDirs + classpath = sourceSets.integrationTestJdbcPermission.runtimeClasspath + outputs.upToDateWhen { false } // ensures integration tests are run every time when called + options { + systemProperties(System.getProperties().findAll { it.key.toString().startsWith("scalardb") }) + } +} + spotless { java { target 'src/*/java/**/*.java' diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java new file mode 100644 index 0000000000..ae955a3a91 --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java @@ -0,0 +1,141 @@ +package com.scalar.db.storage.cassandra; + +import com.google.common.util.concurrent.Uninterruptibles; +import com.scalar.db.api.DistributedStorageAdminPermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class CassandraAdminPermissionIntegrationTest + extends DistributedStorageAdminPermissionIntegrationTestBase { + private static final int SLEEP_BETWEEN_RETRIES_SECONDS = 3; + private static final int MAX_RETRY_COUNT = 10; + + @Override + protected Properties getProperties(String testName) { + return CassandraEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return CassandraEnv.getPropertiesForNormalUser(testName); + } + + @Override + protected Map getCreationOptions() { + return Collections.singletonMap(CassandraAdmin.REPLICATION_FACTOR, "1"); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new CassandraPermissionTestUtils(getProperties(testName)); + } + + @Override + protected void waitForNamespaceCreation() { + try { + AdminTestUtils utils = getAdminTestUtils(TEST_NAME); + int retryCount = 0; + while (retryCount < MAX_RETRY_COUNT) { + if (utils.namespaceExists(NAMESPACE)) { + utils.close(); + return; + } + Uninterruptibles.sleepUninterruptibly( + SLEEP_BETWEEN_RETRIES_SECONDS, java.util.concurrent.TimeUnit.SECONDS); + retryCount++; + } + utils.close(); + throw new RuntimeException("Namespace was not created after " + MAX_RETRY_COUNT + " retries"); + } catch (Exception e) { + throw new RuntimeException("Failed to wait for namespace creation", e); + } + } + + @Override + protected void waitForTableCreation() { + try { + AdminTestUtils utils = getAdminTestUtils(TEST_NAME); + int retryCount = 0; + while (retryCount < MAX_RETRY_COUNT) { + if (utils.tableExists(NAMESPACE, TABLE)) { + utils.close(); + return; + } + Uninterruptibles.sleepUninterruptibly( + SLEEP_BETWEEN_RETRIES_SECONDS, java.util.concurrent.TimeUnit.SECONDS); + retryCount++; + } + utils.close(); + throw new RuntimeException("Table was not created after " + MAX_RETRY_COUNT + " retries"); + } catch (Exception e) { + throw new RuntimeException("Failed to wait for table creation", e); + } + } + + @Override + protected void waitForNamespaceDeletion() { + try { + AdminTestUtils utils = getAdminTestUtils(TEST_NAME); + int retryCount = 0; + while (retryCount < MAX_RETRY_COUNT) { + if (!utils.namespaceExists(NAMESPACE)) { + utils.close(); + return; + } + Uninterruptibles.sleepUninterruptibly( + SLEEP_BETWEEN_RETRIES_SECONDS, java.util.concurrent.TimeUnit.MILLISECONDS); + retryCount++; + } + utils.close(); + throw new RuntimeException("Namespace was not deleted after " + MAX_RETRY_COUNT + " retries"); + } catch (Exception e) { + throw new RuntimeException("Failed to wait for namespace deletion", e); + } + } + + @Override + protected void waitForTableDeletion() { + try { + AdminTestUtils utils = getAdminTestUtils(TEST_NAME); + int retryCount = 0; + while (retryCount < MAX_RETRY_COUNT) { + if (!utils.tableExists(NAMESPACE, TABLE)) { + utils.close(); + return; + } + Uninterruptibles.sleepUninterruptibly( + SLEEP_BETWEEN_RETRIES_SECONDS, java.util.concurrent.TimeUnit.SECONDS); + retryCount++; + } + utils.close(); + throw new RuntimeException("Table was not deleted after " + MAX_RETRY_COUNT + " retries"); + } catch (Exception e) { + throw new RuntimeException("Failed to wait for table deletion", e); + } + } + + @Test + @Override + @Disabled("Import-related functionality is not supported in Cassandra") + public void getImportTableMetadata_WithSufficientPermission_ShouldSucceed() {} + + @Test + @Override + @Disabled("Import-related functionality is not supported in Cassandra") + public void addRawColumnToTable_WithSufficientPermission_ShouldSucceed() {} + + @Test + @Override + @Disabled("Import-related functionality is not supported in Cassandra") + public void importTable_WithSufficientPermission_ShouldSucceed() {} +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraEnv.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraEnv.java index 4af38f013e..da41c00673 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraEnv.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraEnv.java @@ -7,10 +7,14 @@ public final class CassandraEnv { private static final String PROP_CASSANDRA_CONTACT_POINTS = "scalardb.cassandra.contact_points"; private static final String PROP_CASSANDRA_USERNAME = "scalardb.cassandra.username"; private static final String PROP_CASSANDRA_PASSWORD = "scalardb.cassandra.password"; + private static final String PROP_CASSANDRA_NORMAL_USERNAME = "scalardb.cassandra.normal_username"; + private static final String PROP_CASSANDRA_NORMAL_PASSWORD = "scalardb.cassandra.normal_password"; private static final String DEFAULT_CASSANDRA_CONTACT_POINTS = "localhost"; private static final String DEFAULT_CASSANDRA_USERNAME = "cassandra"; private static final String DEFAULT_CASSANDRA_PASSWORD = "cassandra"; + private static final String DEFAULT_CASSANDRA_NORMAL_USERNAME = "test"; + private static final String DEFAULT_CASSANDRA_NORMAL_PASSWORD = "test"; private CassandraEnv() {} @@ -35,4 +39,28 @@ public static Properties getProperties(String testName) { return properties; } + + public static Properties getPropertiesForNormalUser(String testName) { + String contactPoints = + System.getProperty(PROP_CASSANDRA_CONTACT_POINTS, DEFAULT_CASSANDRA_CONTACT_POINTS); + String username = + System.getProperty(PROP_CASSANDRA_NORMAL_USERNAME, DEFAULT_CASSANDRA_NORMAL_USERNAME); + String password = + System.getProperty(PROP_CASSANDRA_NORMAL_PASSWORD, DEFAULT_CASSANDRA_NORMAL_PASSWORD); + + Properties properties = new Properties(); + properties.setProperty(DatabaseConfig.CONTACT_POINTS, contactPoints); + properties.setProperty(DatabaseConfig.USERNAME, username); + properties.setProperty(DatabaseConfig.PASSWORD, password); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN, "true"); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN_FILTERING, "true"); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN_ORDERING, "false"); + + // Add testName as a metadata schema suffix + properties.setProperty( + DatabaseConfig.SYSTEM_NAMESPACE_NAME, + DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME + "_" + testName); + + return properties; + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java new file mode 100644 index 0000000000..213950c9cd --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionIntegrationTest.java @@ -0,0 +1,61 @@ +package com.scalar.db.storage.cassandra; + +import com.google.common.util.concurrent.Uninterruptibles; +import com.scalar.db.api.DistributedStoragePermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +public class CassandraPermissionIntegrationTest + extends DistributedStoragePermissionIntegrationTestBase { + private static final int SLEEP_BETWEEN_RETRIES_SECONDS = 3; + private static final int MAX_RETRY_COUNT = 10; + + @Override + protected Properties getProperties(String testName) { + return CassandraEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return CassandraEnv.getPropertiesForNormalUser(testName); + } + + @Override + protected Map getCreationOptions() { + return Collections.singletonMap(CassandraAdmin.REPLICATION_FACTOR, "1"); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new CassandraPermissionTestUtils(getProperties(testName)); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } + + @Override + protected void waitForTableCreation() { + try { + AdminTestUtils utils = getAdminTestUtils(TEST_NAME); + int retryCount = 0; + while (retryCount < MAX_RETRY_COUNT) { + if (utils.tableExists(NAMESPACE, TABLE)) { + utils.close(); + return; + } + Uninterruptibles.sleepUninterruptibly(SLEEP_BETWEEN_RETRIES_SECONDS, TimeUnit.SECONDS); + retryCount++; + } + utils.close(); + throw new RuntimeException("Table was not created after " + MAX_RETRY_COUNT + " retries"); + } catch (Exception e) { + throw new RuntimeException("Failed to wait for table creation", e); + } + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java new file mode 100644 index 0000000000..d00bd23fff --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraPermissionTestUtils.java @@ -0,0 +1,52 @@ +package com.scalar.db.storage.cassandra; + +import com.datastax.driver.core.Session; +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Properties; + +public class CassandraPermissionTestUtils implements PermissionTestUtils { + private final ClusterManager clusterManager; + + public CassandraPermissionTestUtils(Properties properties) { + DatabaseConfig databaseConfig = new DatabaseConfig(properties); + clusterManager = new ClusterManager(databaseConfig); + } + + @Override + public void createNormalUser(String userName, String password) { + clusterManager + .getSession() + .execute( + String.format( + "CREATE ROLE %s WITH PASSWORD = '%s' AND LOGIN = true", userName, password)); + } + + @Override + public void dropNormalUser(String userName) { + clusterManager.getSession().execute(String.format("DROP ROLE %s", userName)); + } + + @Override + public void grantRequiredPermission(String userName) { + Session session = clusterManager.getSession(); + for (String grantStatement : getGrantPermissionStatements(userName)) { + session.execute(grantStatement); + } + } + + private String[] getGrantPermissionStatements(String userName) { + return new String[] { + String.format("GRANT CREATE ON ALL KEYSPACES TO %s", userName), + String.format("GRANT DROP ON ALL KEYSPACES TO %s", userName), + String.format("GRANT ALTER ON ALL KEYSPACES TO %s", userName), + String.format("GRANT SELECT ON ALL KEYSPACES TO %s", userName), + String.format("GRANT MODIFY ON ALL KEYSPACES TO %s", userName) + }; + } + + @Override + public void close() { + clusterManager.close(); + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java new file mode 100644 index 0000000000..67e0671974 --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java @@ -0,0 +1,62 @@ +package com.scalar.db.storage.dynamo; + +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Uninterruptibles; +import com.scalar.db.api.DistributedStorageAdminPermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class DynamoAdminPermissionIntegrationTest + extends DistributedStorageAdminPermissionIntegrationTestBase { + private static final int SLEEP_BETWEEN_TESTS_SECONDS = 10; + + @Override + protected Properties getProperties(String testName) { + return DynamoEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return DynamoEnv.getProperties(testName); + } + + @Override + protected Map getCreationOptions() { + return ImmutableMap.of(DynamoAdmin.NO_SCALING, "false", DynamoAdmin.NO_BACKUP, "false"); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new DynamoAdminTestUtils(getProperties(testName)); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new DynamoPermissionTestUtils(getProperties(testName)); + } + + @Override + protected void sleepBetweenTests() { + Uninterruptibles.sleepUninterruptibly(SLEEP_BETWEEN_TESTS_SECONDS, TimeUnit.SECONDS); + } + + @Test + @Override + @Disabled("Import-related functionality is not supported in DynamoDB") + public void getImportTableMetadata_WithSufficientPermission_ShouldSucceed() {} + + @Test + @Override + @Disabled("Import-related functionality is not supported in DynamoDB") + public void addRawColumnToTable_WithSufficientPermission_ShouldSucceed() {} + + @Test + @Override + @Disabled("Import-related functionality is not supported in DynamoDB") + public void importTable_WithSufficientPermission_ShouldSucceed() {} +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoEnv.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoEnv.java index cf7e719dfb..a978a2e3d0 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoEnv.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoEnv.java @@ -10,12 +10,14 @@ public final class DynamoEnv { private static final String PROP_DYNAMO_REGION = "scalardb.dynamo.region"; private static final String PROP_DYNAMO_ACCESS_KEY_ID = "scalardb.dynamo.access_key_id"; private static final String PROP_DYNAMO_SECRET_ACCESS_KEY = "scalardb.dynamo.secret_access_key"; + private static final String PROP_DYNAMO_EMULATOR = "scalardb.dynamo.emulator"; private static final String PROP_DYNAMO_CREATE_OPTIONS = "scalardb.dynamo.create_options"; private static final String DEFAULT_DYNAMO_ENDPOINT_OVERRIDE = "http://localhost:8000"; private static final String DEFAULT_DYNAMO_REGION = "us-west-2"; private static final String DEFAULT_DYNAMO_ACCESS_KEY_ID = "fakeMyKeyId"; private static final String DEFAULT_DYNAMO_SECRET_ACCESS_KEY = "fakeSecretAccessKey"; + private static final String DEFAULT_DYNAMO_EMULATOR = "true"; private static final ImmutableMap DEFAULT_DYNAMO_CREATE_OPTIONS = ImmutableMap.of(DynamoAdmin.NO_SCALING, "true", DynamoAdmin.NO_BACKUP, "true"); @@ -30,9 +32,10 @@ public static Properties getProperties(String testName) { System.getProperty(PROP_DYNAMO_ACCESS_KEY_ID, DEFAULT_DYNAMO_ACCESS_KEY_ID); String secretAccessKey = System.getProperty(PROP_DYNAMO_SECRET_ACCESS_KEY, DEFAULT_DYNAMO_SECRET_ACCESS_KEY); + String isEmulator = System.getProperty(PROP_DYNAMO_EMULATOR, DEFAULT_DYNAMO_EMULATOR); Properties properties = new Properties(); - if (endpointOverride != null) { + if (Boolean.parseBoolean(isEmulator) && endpointOverride != null) { properties.setProperty(DynamoConfig.ENDPOINT_OVERRIDE, endpointOverride); } properties.setProperty(DatabaseConfig.CONTACT_POINTS, region); diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java new file mode 100644 index 0000000000..beb5816f5b --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java @@ -0,0 +1,36 @@ +package com.scalar.db.storage.dynamo; + +import com.google.common.collect.ImmutableMap; +import com.scalar.db.api.DistributedStoragePermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Map; +import java.util.Properties; + +public class DynamoPermissionIntegrationTest + extends DistributedStoragePermissionIntegrationTestBase { + @Override + protected Properties getProperties(String testName) { + return DynamoEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return DynamoEnv.getProperties(testName); + } + + @Override + protected Map getCreationOptions() { + return ImmutableMap.of(DynamoAdmin.NO_SCALING, "false", DynamoAdmin.NO_BACKUP, "false"); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new DynamoPermissionTestUtils(getProperties(testName)); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new DynamoAdminTestUtils(getProperties(testName)); + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java new file mode 100644 index 0000000000..015e08dc69 --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java @@ -0,0 +1,145 @@ +package com.scalar.db.storage.dynamo; + +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Optional; +import java.util.Properties; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.policybuilder.iam.IamEffect; +import software.amazon.awssdk.policybuilder.iam.IamPolicy; +import software.amazon.awssdk.policybuilder.iam.IamResource; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.model.AttachUserPolicyRequest; +import software.amazon.awssdk.services.iam.model.AttachedPolicy; +import software.amazon.awssdk.services.iam.model.CreatePolicyRequest; +import software.amazon.awssdk.services.iam.model.CreatePolicyVersionRequest; +import software.amazon.awssdk.services.iam.model.DeletePolicyVersionRequest; +import software.amazon.awssdk.services.iam.model.ListAttachedUserPoliciesRequest; +import software.amazon.awssdk.services.iam.model.ListPolicyVersionsRequest; +import software.amazon.awssdk.services.iam.model.User; + +public class DynamoPermissionTestUtils implements PermissionTestUtils { + private static final String IAM_POLICY_NAME = "test-dynamodb-permissions"; + private static final IamPolicy POLICY = + IamPolicy.builder() + .addStatement( + s -> + s.effect(IamEffect.ALLOW) + .addAction("dynamodb:ConditionCheckItem") + .addAction("dynamodb:PutItem") + .addAction("dynamodb:ListTables") + .addAction("dynamodb:DeleteItem") + .addAction("dynamodb:Scan") + .addAction("dynamodb:Query") + .addAction("dynamodb:UpdateItem") + .addAction("dynamodb:DeleteTable") + .addAction("dynamodb:UpdateContinuousBackups") + .addAction("dynamodb:CreateTable") + .addAction("dynamodb:DescribeTable") + .addAction("dynamodb:GetItem") + .addAction("dynamodb:DescribeContinuousBackups") + .addAction("dynamodb:UpdateTable") + .addAction("application-autoscaling:RegisterScalableTarget") + .addAction("application-autoscaling:DeleteScalingPolicy") + .addAction("application-autoscaling:PutScalingPolicy") + .addAction("application-autoscaling:DeregisterScalableTarget") + .addAction("application-autoscaling:TagResource") + .addResource(IamResource.ALL)) + .build(); + private final IamClient client; + + public DynamoPermissionTestUtils(Properties properties) { + DynamoConfig config = new DynamoConfig(new DatabaseConfig(properties)); + this.client = + IamClient.builder() + .region(Region.of(config.getRegion())) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create( + config.getAccessKeyId(), config.getSecretAccessKey()))) + .build(); + } + + @Override + public void createNormalUser(String userName, String password) { + // Do nothing for DynamoDB. + } + + @Override + public void dropNormalUser(String userName) { + // Do nothing for DynamoDB. + } + + @Override + public void grantRequiredPermission(String userName) { + try { + User user = client.getUser().user(); + Optional attachedPolicyArn = getAttachedPolicyArn(user.userName(), IAM_POLICY_NAME); + if (attachedPolicyArn.isPresent()) { + deleteStalePolicyVersions(attachedPolicyArn.get()); + createNewPolicyVersion(attachedPolicyArn.get()); + } else { + String policyArn = createNewPolicy(); + client.attachUserPolicy( + AttachUserPolicyRequest.builder() + .userName(user.userName()) + .policyArn(policyArn) + .build()); + } + } catch (Exception e) { + throw new RuntimeException("Failed to grant required permissions", e); + } + } + + @Override + public void close() { + client.close(); + } + + private Optional getAttachedPolicyArn(String userName, String policyName) { + AttachedPolicy attachedPolicy = + client + .listAttachedUserPolicies( + ListAttachedUserPoliciesRequest.builder().userName(userName).build()) + .attachedPolicies().stream() + .filter(policy -> policy.policyName().equals(policyName)) + .findFirst() + .orElse(null); + return Optional.ofNullable(attachedPolicy).map(AttachedPolicy::policyArn); + } + + private String createNewPolicy() { + return client + .createPolicy( + CreatePolicyRequest.builder() + .policyName(IAM_POLICY_NAME) + .policyDocument(POLICY.toJson()) + .build()) + .policy() + .arn(); + } + + private void deleteStalePolicyVersions(String policyArn) { + client.listPolicyVersions(ListPolicyVersionsRequest.builder().policyArn(policyArn).build()) + .versions().stream() + .filter(version -> !version.isDefaultVersion()) + .forEach( + version -> + client.deletePolicyVersion( + DeletePolicyVersionRequest.builder() + .policyArn(policyArn) + .versionId(version.versionId()) + .build())); + } + + private void createNewPolicyVersion(String policyArn) { + client.createPolicyVersion( + CreatePolicyVersionRequest.builder() + .policyArn(policyArn) + .policyDocument(POLICY.toJson()) + .setAsDefault(true) + .build()); + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java new file mode 100644 index 0000000000..d3aa5f9392 --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java @@ -0,0 +1,29 @@ +package com.scalar.db.storage.jdbc; + +import com.scalar.db.api.DistributedStorageAdminPermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Properties; + +public class JdbcAdminPermissionIntegrationTest + extends DistributedStorageAdminPermissionIntegrationTestBase { + @Override + protected Properties getProperties(String testName) { + return JdbcEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return JdbcEnv.getPropertiesForNormalUser(testName); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(getProperties(testName)); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new JdbcPermissionTestUtils(getProperties(testName)); + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java index 8c297435bc..34d4b326cf 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java @@ -7,10 +7,14 @@ public final class JdbcEnv { private static final String PROP_JDBC_URL = "scalardb.jdbc.url"; private static final String PROP_JDBC_USERNAME = "scalardb.jdbc.username"; private static final String PROP_JDBC_PASSWORD = "scalardb.jdbc.password"; + private static final String PROP_JDBC_NORMAL_USERNAME = "scalardb.jdbc.normal_username"; + private static final String PROP_JDBC_NORMAL_PASSWORD = "scalardb.jdbc.normal_password"; private static final String DEFAULT_JDBC_URL = "jdbc:mysql://localhost:3306/"; private static final String DEFAULT_JDBC_USERNAME = "root"; private static final String DEFAULT_JDBC_PASSWORD = "mysql"; + private static final String DEFAULT_JDBC_NORMAL_USERNAME = "test"; + private static final String DEFAULT_JDBC_NORMAL_PASSWORD = "test"; private JdbcEnv() {} @@ -36,6 +40,28 @@ public static Properties getProperties(String testName) { return properties; } + public static Properties getPropertiesForNormalUser(String testName) { + String jdbcUrl = System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL); + String username = System.getProperty(PROP_JDBC_NORMAL_USERNAME, DEFAULT_JDBC_NORMAL_USERNAME); + String password = System.getProperty(PROP_JDBC_NORMAL_PASSWORD, DEFAULT_JDBC_NORMAL_PASSWORD); + + Properties properties = new Properties(); + properties.setProperty(DatabaseConfig.CONTACT_POINTS, jdbcUrl); + properties.setProperty(DatabaseConfig.USERNAME, username); + properties.setProperty(DatabaseConfig.PASSWORD, password); + properties.setProperty(DatabaseConfig.STORAGE, "jdbc"); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN, "true"); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN_FILTERING, "true"); + properties.setProperty(DatabaseConfig.CROSS_PARTITION_SCAN_ORDERING, "true"); + + // Add testName as a metadata schema suffix + properties.setProperty( + DatabaseConfig.SYSTEM_NAMESPACE_NAME, + DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME + "_" + testName); + + return properties; + } + public static boolean isSqlite() { Properties properties = new Properties(); properties.setProperty( diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java new file mode 100644 index 0000000000..84d7646cbe --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionIntegrationTest.java @@ -0,0 +1,28 @@ +package com.scalar.db.storage.jdbc; + +import com.scalar.db.api.DistributedStoragePermissionIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Properties; + +public class JdbcPermissionIntegrationTest extends DistributedStoragePermissionIntegrationTestBase { + @Override + protected Properties getProperties(String testName) { + return JdbcEnv.getProperties(testName); + } + + @Override + protected Properties getPropertiesForNormalUser(String testName) { + return JdbcEnv.getPropertiesForNormalUser(testName); + } + + @Override + protected PermissionTestUtils getPermissionTestUtils(String testName) { + return new JdbcPermissionTestUtils(getProperties(testName)); + } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(getProperties(testName)); + } +} diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java new file mode 100644 index 0000000000..e2c968e4ba --- /dev/null +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcPermissionTestUtils.java @@ -0,0 +1,99 @@ +package com.scalar.db.storage.jdbc; + +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.PermissionTestUtils; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import org.apache.commons.dbcp2.BasicDataSource; + +public class JdbcPermissionTestUtils implements PermissionTestUtils { + private final RdbEngineStrategy rdbEngine; + private final BasicDataSource dataSource; + + public JdbcPermissionTestUtils(Properties properties) { + JdbcConfig config = new JdbcConfig(new DatabaseConfig(properties)); + rdbEngine = RdbEngineFactory.create(config); + dataSource = JdbcUtils.initDataSourceForAdmin(config, rdbEngine); + } + + @Override + public void createNormalUser(String userName, String password) { + try (Connection connection = dataSource.getConnection()) { + String createUserSql = getCreateUserSql(userName, password); + try (Statement statement = connection.createStatement()) { + statement.execute(createUserSql); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to create user: " + userName, e); + } + } + + @Override + public void dropNormalUser(String userName) { + try (Connection connection = dataSource.getConnection()) { + String dropUserSql = getDropUserSql(userName); + try (Statement statement = connection.createStatement()) { + statement.execute(dropUserSql); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to drop user: " + userName, e); + } + } + + @Override + public void grantRequiredPermission(String userName) { + try (Connection connection = dataSource.getConnection()) { + String[] grantStatements = getGrantPermissionStatements(userName); + try (Statement statement = connection.createStatement()) { + for (String sql : grantStatements) { + statement.execute(sql); + } + } + } catch (SQLException e) { + throw new RuntimeException("Failed to grant permissions to user: " + userName, e); + } + } + + private String getCreateUserSql(String userName, String password) { + if (!(rdbEngine instanceof RdbEngineMysql)) { + throw new UnsupportedOperationException("Creating users is only supported for MySQL"); + } + return String.format( + "CREATE USER IF NOT EXISTS '%s'@'%%' IDENTIFIED BY '%s'", userName, password); + } + + private String getDropUserSql(String userName) { + if (!(rdbEngine instanceof RdbEngineMysql)) { + throw new UnsupportedOperationException("Dropping users is only supported for MySQL"); + } + return String.format("DROP USER IF EXISTS '%s'@'%%'", userName); + } + + private String[] getGrantPermissionStatements(String userName) { + if (!(rdbEngine instanceof RdbEngineMysql)) { + throw new UnsupportedOperationException("Granting permissions is only supported for MySQL"); + } + return new String[] { + String.format("GRANT CREATE ON *.* TO '%s'@'%%'", userName), + String.format("GRANT DROP ON *.* TO '%s'@'%%'", userName), + String.format("GRANT INDEX ON *.* TO '%s'@'%%'", userName), + String.format("GRANT ALTER ON *.* TO '%s'@'%%'", userName), + String.format("GRANT SELECT ON *.* TO '%s'@'%%'", userName), + String.format("GRANT INSERT ON *.* TO '%s'@'%%'", userName), + String.format("GRANT UPDATE ON *.* TO '%s'@'%%'", userName), + String.format("GRANT DELETE ON *.* TO '%s'@'%%'", userName), + "FLUSH PRIVILEGES" + }; + } + + @Override + public void close() { + try { + dataSource.close(); + } catch (Exception e) { + throw new RuntimeException("Failed to close the data source", e); + } + } +} diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java new file mode 100644 index 0000000000..f283f5f138 --- /dev/null +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java @@ -0,0 +1,380 @@ +package com.scalar.db.api; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.io.DataType; +import com.scalar.db.service.StorageFactory; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class DistributedStorageAdminPermissionIntegrationTestBase { + + protected static final String TEST_NAME = "storage_admin"; + protected static final String NAMESPACE = "test_" + TEST_NAME + "_1"; + protected static final String TABLE = "test_table_1"; + private static final String COL_NAME1 = "c1"; + private static final String COL_NAME2 = "c2"; + private static final String COL_NAME3 = "c3"; + private static final String COL_NAME4 = "c4"; + private static final String RAW_COL_NAME = "raw_col"; + private static final String NEW_COL_NAME = "new_col"; + private static final TableMetadata TABLE_METADATA = + TableMetadata.newBuilder() + .addColumn(COL_NAME1, DataType.INT) + .addColumn(COL_NAME2, DataType.TEXT) + .addColumn(COL_NAME3, DataType.TEXT) + .addColumn(COL_NAME4, DataType.INT) + .addPartitionKey(COL_NAME1) + .addClusteringKey(COL_NAME2, Scan.Ordering.Order.ASC) + .addSecondaryIndex(COL_NAME4) + .build(); + protected DistributedStorageAdmin adminForRootUser; + private DistributedStorageAdmin adminForNormalUser; + private String normalUserName; + + @BeforeAll + public void beforeAll() throws Exception { + Properties propertiesForRootUser = getProperties(TEST_NAME); + Properties propertiesForNormalUser = getPropertiesForNormalUser(TEST_NAME); + + // Initialize the admin for root user + StorageFactory factoryForRootUser = StorageFactory.create(propertiesForRootUser); + adminForRootUser = factoryForRootUser.getStorageAdmin(); + + DatabaseConfig config = new DatabaseConfig(propertiesForNormalUser); + if (!config.getUsername().isPresent() || !config.getPassword().isPresent()) { + throw new IllegalArgumentException( + "Username and password must be set in the properties for normal user"); + } + // Create normal user and grant permissions + normalUserName = config.getUsername().get(); + PermissionTestUtils permissionTestUtils = getPermissionTestUtils(TEST_NAME); + try { + permissionTestUtils.createNormalUser(normalUserName, config.getPassword().get()); + permissionTestUtils.grantRequiredPermission(normalUserName); + } finally { + permissionTestUtils.close(); + } + + // Initialize the admin for normal user + StorageFactory factoryForNormalUser = StorageFactory.create(propertiesForNormalUser); + adminForNormalUser = factoryForNormalUser.getStorageAdmin(); + } + + @AfterAll + public void afterAll() throws Exception { + // Drop the table and namespace created for the tests + adminForRootUser.dropTable(NAMESPACE, TABLE, true); + adminForRootUser.dropNamespace(NAMESPACE, true); + // Close the admin instances + adminForRootUser.close(); + adminForNormalUser.close(); + // Drop normal user + PermissionTestUtils permissionTestUtils = getPermissionTestUtils(TEST_NAME); + try { + permissionTestUtils.dropNormalUser(normalUserName); + } finally { + permissionTestUtils.close(); + } + } + + @BeforeEach + public void beforeEach() throws ExecutionException { + dropTableByRootIfExists(); + dropNamespaceByRootIfExists(); + } + + @AfterEach + public void afterEach() { + sleepBetweenTests(); + } + + @Test + public void getImportTableMetadata_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.getImportTableMetadata(NAMESPACE, TABLE)) + .doesNotThrowAnyException(); + } + + @Test + public void addRawColumnToTable_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode( + () -> + adminForNormalUser.addRawColumnToTable( + NAMESPACE, TABLE, RAW_COL_NAME, DataType.INT)) + .doesNotThrowAnyException(); + } + + @Test + public void createNamespace_WithSufficientPermission_ShouldSucceed() { + // Arrange + // Act Assert + assertThatCode(() -> adminForNormalUser.createNamespace(NAMESPACE, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void createTable_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + // Act Assert + assertThatCode( + () -> + adminForNormalUser.createTable( + NAMESPACE, TABLE, TABLE_METADATA, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void dropTable_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.dropTable(NAMESPACE, TABLE)).doesNotThrowAnyException(); + } + + @Test + public void dropNamespace_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.dropNamespace(NAMESPACE, true)) + .doesNotThrowAnyException(); + } + + @Test + public void truncateTable_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.truncateTable(NAMESPACE, TABLE)) + .doesNotThrowAnyException(); + } + + @Test + public void createIndex_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode( + () -> adminForNormalUser.createIndex(NAMESPACE, TABLE, COL_NAME3, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void dropIndex_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.dropIndex(NAMESPACE, TABLE, COL_NAME4)) + .doesNotThrowAnyException(); + } + + @Test + public void indexExists_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.indexExists(NAMESPACE, TABLE, COL_NAME4)) + .doesNotThrowAnyException(); + } + + @Test + public void getTableMetadata_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.getTableMetadata(NAMESPACE, TABLE)) + .doesNotThrowAnyException(); + } + + @Test + public void getNamespaceTableNames_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.getNamespaceTableNames(NAMESPACE)) + .doesNotThrowAnyException(); + } + + @Test + public void namespaceExists_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.namespaceExists(NAMESPACE)).doesNotThrowAnyException(); + } + + @Test + public void tableExists_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.tableExists(NAMESPACE, TABLE)) + .doesNotThrowAnyException(); + } + + @Test + public void repairNamespace_WithSufficientPermission_ShouldSucceed() throws Exception { + // Arrange + createNamespaceByRoot(); + // Drop the namespaces table to simulate a repair scenario + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + adminTestUtils.dropNamespacesTable(); + } finally { + adminTestUtils.close(); + } + // Act Assert + assertThatCode(() -> adminForNormalUser.repairNamespace(NAMESPACE, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void repairTable_WithSufficientPermission_ShouldSucceed() throws Exception { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Drop the metadata table to simulate a repair scenario + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + adminTestUtils.dropMetadataTable(); + } finally { + adminTestUtils.close(); + } + // Act Assert + assertThatCode( + () -> + adminForNormalUser.repairTable( + NAMESPACE, TABLE, TABLE_METADATA, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void addNewColumnToTable_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + // Act Assert + assertThatCode( + () -> + adminForNormalUser.addNewColumnToTable( + NAMESPACE, TABLE, NEW_COL_NAME, DataType.INT)) + .doesNotThrowAnyException(); + } + + @Test + public void importTable_WithSufficientPermission_ShouldSucceed() throws Exception { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + adminTestUtils.dropNamespacesTable(); + adminTestUtils.dropMetadataTable(); + } finally { + adminTestUtils.close(); + } + // Act Assert + assertThatCode(() -> adminForNormalUser.importTable(NAMESPACE, TABLE, getCreationOptions())) + .doesNotThrowAnyException(); + } + + @Test + public void getNamespaceNames_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + // Act Assert + assertThatCode(() -> adminForNormalUser.getNamespaceNames()).doesNotThrowAnyException(); + } + + @Test + public void upgrade_WithSufficientPermission_ShouldSucceed() throws Exception { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + adminTestUtils.dropNamespacesTable(); + } finally { + adminTestUtils.close(); + } + // Act Assert + assertThatCode(() -> adminForNormalUser.upgrade(getCreationOptions())) + .doesNotThrowAnyException(); + } + + protected abstract Properties getProperties(String testName); + + protected abstract Properties getPropertiesForNormalUser(String testName); + + protected Map getCreationOptions() { + return Collections.emptyMap(); + } + + protected abstract AdminTestUtils getAdminTestUtils(String testName); + + protected abstract PermissionTestUtils getPermissionTestUtils(String testName); + + protected void waitForTableCreation() {} + + protected void waitForNamespaceCreation() {} + + protected void waitForTableDeletion() {} + + protected void waitForNamespaceDeletion() {} + + protected void sleepBetweenTests() {} + + private void createNamespaceByRoot() throws ExecutionException { + adminForRootUser.createNamespace(NAMESPACE, getCreationOptions()); + waitForNamespaceCreation(); + } + + private void createTableByRoot() throws ExecutionException { + adminForRootUser.createTable(NAMESPACE, TABLE, TABLE_METADATA, getCreationOptions()); + waitForTableCreation(); + } + + private void dropNamespaceByRootIfExists() throws ExecutionException { + adminForRootUser.dropNamespace(NAMESPACE, true); + waitForNamespaceDeletion(); + } + + private void dropTableByRootIfExists() throws ExecutionException { + adminForRootUser.dropTable(NAMESPACE, TABLE, true); + waitForTableDeletion(); + } +} diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStoragePermissionIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStoragePermissionIntegrationTestBase.java new file mode 100644 index 0000000000..f42454bcba --- /dev/null +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStoragePermissionIntegrationTestBase.java @@ -0,0 +1,310 @@ +package com.scalar.db.api; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.io.DataType; +import com.scalar.db.io.IntColumn; +import com.scalar.db.io.Key; +import com.scalar.db.service.StorageFactory; +import com.scalar.db.util.AdminTestUtils; +import com.scalar.db.util.PermissionTestUtils; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class DistributedStoragePermissionIntegrationTestBase { + protected static final String TEST_NAME = "storage"; + protected static final String NAMESPACE = "int_test_" + TEST_NAME; + protected static final String TABLE = "test_table"; + + private static final String COL_NAME1 = "c1"; + private static final String COL_NAME2 = "c2"; + private static final String COL_NAME3 = "c3"; + private static final int PARTITION_KEY_VALUE = 1; + private static final String CLUSTERING_KEY_VALUE1 = "value1"; + private static final String CLUSTERING_KEY_VALUE2 = "value2"; + private static final int INT_COLUMN_VALUE1 = 1; + private static final int INT_COLUMN_VALUE2 = 1; + + private String normalUserName; + private DistributedStorage storageForNormalUser; + private DistributedStorageAdmin adminForRootUser; + private String namespace; + + @BeforeAll + public void beforeAll() throws Exception { + Properties propertiesForRootUser = getProperties(TEST_NAME); + Properties propertiesForNormalUser = getPropertiesForNormalUser(TEST_NAME); + + // Create admin for root user + StorageFactory factoryForRootUser = StorageFactory.create(propertiesForRootUser); + adminForRootUser = factoryForRootUser.getStorageAdmin(); + + DatabaseConfig config = new DatabaseConfig(propertiesForNormalUser); + if (!config.getUsername().isPresent() || !config.getPassword().isPresent()) { + throw new IllegalArgumentException( + "Username and password must be set in the properties for normal user"); + } + // Create normal user and grant permissions + PermissionTestUtils permissionTestUtils = getPermissionTestUtils(TEST_NAME); + normalUserName = config.getUsername().get(); + permissionTestUtils.createNormalUser(normalUserName, config.getPassword().get()); + permissionTestUtils.grantRequiredPermission(normalUserName); + permissionTestUtils.close(); + + // Create storage for normal user + StorageFactory factoryForNormalUser = StorageFactory.create(propertiesForNormalUser); + storageForNormalUser = factoryForNormalUser.getStorage(); + + namespace = getNamespace(); + createTable(); + waitForTableCreation(); + } + + @BeforeEach + public void setUp() throws Exception { + truncateTable(); + } + + @AfterAll + public void afterAll() throws Exception { + dropTable(); + storageForNormalUser.close(); + adminForRootUser.close(); + // Drop normal user + PermissionTestUtils permissionTestUtils = getPermissionTestUtils(TEST_NAME); + permissionTestUtils.dropNormalUser(normalUserName); + permissionTestUtils.close(); + } + + @Test + public void get_WithSufficientPermission_ShouldSucceed() { + // Arrange + Get get = + Get.newBuilder() + .namespace(namespace) + .table(TABLE) + .partitionKey(Key.ofInt(COL_NAME1, PARTITION_KEY_VALUE)) + .clusteringKey(Key.ofText(COL_NAME2, CLUSTERING_KEY_VALUE1)) + .build(); + // Act Assert + assertThatCode(() -> storageForNormalUser.get(get)).doesNotThrowAnyException(); + } + + @Test + public void scan_WithSufficientPermission_ShouldSucceed() { + // Arrange + Scan scan = + Scan.newBuilder() + .namespace(namespace) + .table(TABLE) + .partitionKey(Key.ofInt(COL_NAME1, PARTITION_KEY_VALUE)) + .build(); + // Act Assert + assertThatCode(() -> storageForNormalUser.scan(scan).close()).doesNotThrowAnyException(); + } + + @Test + public void scanAll_WithSufficientPermission_ShouldSucceed() { + // Arrange + Scan scan = Scan.newBuilder().namespace(namespace).table(TABLE).all().build(); + // Act Assert + assertThatCode(() -> storageForNormalUser.scan(scan).close()).doesNotThrowAnyException(); + } + + @Test + public void put_WithoutCondition_WithSufficientPermission_ShouldSucceed() { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + // Act Assert + assertThatCode(() -> storageForNormalUser.put(put)).doesNotThrowAnyException(); + } + + @Test + public void put_WithPutIfNotExists_WithSufficientPermission_ShouldSucceed() { + // Arrange + Put putWithPutIfNotExists = + createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, ConditionBuilder.putIfNotExists()); + // Act Assert + assertThatCode(() -> storageForNormalUser.put(putWithPutIfNotExists)) + .doesNotThrowAnyException(); + } + + @Test + public void put_WithPutIfExists_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + storageForNormalUser.put(put); + Put putWithPutIfExists = + createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE2, ConditionBuilder.putIfExists()); + // Act Assert + assertThatCode(() -> storageForNormalUser.put(putWithPutIfExists)).doesNotThrowAnyException(); + } + + @Test + public void put_WithPutIf_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + storageForNormalUser.put(put); + ConditionalExpression conditionalExpression = + ConditionBuilder.buildConditionalExpression( + IntColumn.of(COL_NAME3, INT_COLUMN_VALUE1), ConditionalExpression.Operator.EQ); + Put putWithPutIf = + createPut( + CLUSTERING_KEY_VALUE1, + INT_COLUMN_VALUE2, + ConditionBuilder.putIf(conditionalExpression).build()); + // Act Assert + assertThatCode(() -> storageForNormalUser.put(putWithPutIf)).doesNotThrowAnyException(); + } + + @Test + public void put_WithMultiplePuts_WithSufficientPermission_ShouldSucceed() { + // Arrange + Put put1 = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + Put put2 = createPut(CLUSTERING_KEY_VALUE2, INT_COLUMN_VALUE2, null); + // Act Assert + assertThatCode(() -> storageForNormalUser.put(Arrays.asList(put1, put2))) + .doesNotThrowAnyException(); + } + + @Test + public void delete_WithSufficientPermission_ShouldSucceed() { + // Arrange + Delete delete = createDelete(CLUSTERING_KEY_VALUE1, null); + // Act Assert + assertThatCode(() -> storageForNormalUser.delete(delete)).doesNotThrowAnyException(); + } + + @Test + public void delete_WithDeleteIfExists_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + storageForNormalUser.put(put); + Delete delete = createDelete(CLUSTERING_KEY_VALUE1, ConditionBuilder.deleteIfExists()); + // Act Assert + assertThatCode(() -> storageForNormalUser.delete(delete)).doesNotThrowAnyException(); + } + + @Test + public void delete_WithDeleteIf_WithSufficientPermission_ShouldSucceed() + throws ExecutionException { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + storageForNormalUser.put(put); + ConditionalExpression conditionalExpression = + ConditionBuilder.buildConditionalExpression( + IntColumn.of(COL_NAME3, INT_COLUMN_VALUE1), ConditionalExpression.Operator.EQ); + Delete delete = + createDelete( + CLUSTERING_KEY_VALUE1, ConditionBuilder.deleteIf(conditionalExpression).build()); + // Act Assert + assertThatCode(() -> storageForNormalUser.delete(delete)).doesNotThrowAnyException(); + } + + @Test + public void delete_WithMultipleDeletes_WithSufficientPermission_ShouldSucceed() { + // Arrange + Delete delete1 = createDelete(CLUSTERING_KEY_VALUE1, null); + Delete delete2 = createDelete(CLUSTERING_KEY_VALUE2, null); + // Act Assert + assertThatCode(() -> storageForNormalUser.delete(Arrays.asList(delete1, delete2))) + .doesNotThrowAnyException(); + } + + @Test + public void mutate_WithSufficientPermission_ShouldSucceed() { + // Arrange + Put put = createPut(CLUSTERING_KEY_VALUE1, INT_COLUMN_VALUE1, null); + Delete delete = createDelete(CLUSTERING_KEY_VALUE2, null); + // Act Assert + assertThatCode(() -> storageForNormalUser.mutate(Arrays.asList(put, delete))) + .doesNotThrowAnyException(); + } + + protected abstract Properties getProperties(String testName); + + protected abstract Properties getPropertiesForNormalUser(String testName); + + protected String getNamespace() { + return NAMESPACE; + } + + protected Map getCreationOptions() { + return Collections.emptyMap(); + } + + protected abstract PermissionTestUtils getPermissionTestUtils(String testName); + + protected abstract AdminTestUtils getAdminTestUtils(String testName); + + protected void waitForTableCreation() {} + + private void createTable() throws ExecutionException { + Map options = getCreationOptions(); + adminForRootUser.createNamespace(namespace, true, options); + adminForRootUser.createTable( + namespace, + TABLE, + TableMetadata.newBuilder() + .addColumn(COL_NAME1, DataType.INT) + .addColumn(COL_NAME2, DataType.TEXT) + .addColumn(COL_NAME3, DataType.INT) + .addPartitionKey(COL_NAME1) + .addClusteringKey(COL_NAME2) + .build(), + true, + options); + } + + private void truncateTable() throws ExecutionException { + adminForRootUser.truncateTable(namespace, TABLE); + } + + private void dropTable() throws ExecutionException { + adminForRootUser.dropTable(namespace, TABLE); + adminForRootUser.dropNamespace(namespace); + } + + private Put createPut(String clusteringKey, int intColumnValue, MutationCondition condition) { + PutBuilder.Buildable buildable = + Put.newBuilder() + .namespace(namespace) + .table(TABLE) + .partitionKey( + Key.ofInt( + COL_NAME1, DistributedStoragePermissionIntegrationTestBase.PARTITION_KEY_VALUE)) + .clusteringKey(Key.ofText(COL_NAME2, clusteringKey)) + .intValue(COL_NAME3, intColumnValue); + if (condition != null) { + buildable.condition(condition); + } + return buildable.build(); + } + + private Delete createDelete(String clusteringKey, MutationCondition condition) { + DeleteBuilder.Buildable buildable = + Delete.newBuilder() + .namespace(namespace) + .table(TABLE) + .partitionKey( + Key.ofInt( + COL_NAME1, DistributedStoragePermissionIntegrationTestBase.PARTITION_KEY_VALUE)) + .clusteringKey(Key.ofText(COL_NAME2, clusteringKey)); + if (condition != null) { + buildable.condition(condition); + } + return buildable.build(); + } +} diff --git a/integration-test/src/main/java/com/scalar/db/util/PermissionTestUtils.java b/integration-test/src/main/java/com/scalar/db/util/PermissionTestUtils.java new file mode 100644 index 0000000000..085ffa37b0 --- /dev/null +++ b/integration-test/src/main/java/com/scalar/db/util/PermissionTestUtils.java @@ -0,0 +1,12 @@ +package com.scalar.db.util; + +public interface PermissionTestUtils { + + void createNormalUser(String userName, String password); + + void dropNormalUser(String userName); + + void grantRequiredPermission(String userName); + + void close(); +}