io.stargate.it.bridge.BridgeAuthorizationTest Maven / Gradle / Ivy
package io.stargate.it.bridge;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.StringValue;
import io.grpc.StatusRuntimeException;
import io.stargate.bridge.proto.QueryOuterClass;
import io.stargate.bridge.proto.Schema;
import io.stargate.it.driver.CqlSessionExtension;
import io.stargate.it.driver.CqlSessionSpec;
import io.stargate.it.storage.StargateConnectionInfo;
import io.stargate.it.storage.StargateParameters;
import io.stargate.it.storage.StargateSpec;
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@StargateSpec(parametersCustomizer = "buildParameters")
@ExtendWith(CqlSessionExtension.class)
@CqlSessionSpec(
initQueries = {
"CREATE ROLE IF NOT EXISTS 'read_only_user' WITH PASSWORD = 'read_only_user' AND LOGIN = TRUE",
"CREATE ROLE IF NOT EXISTS 'not_even_reads_user' WITH PASSWORD = 'tiger' AND LOGIN = TRUE",
"CREATE KEYSPACE IF NOT EXISTS bridge_table_token_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':'1'}",
"CREATE TABLE IF NOT EXISTS bridge_table_token_test.tbl_test (key text PRIMARY KEY, value text)",
"INSERT INTO bridge_table_token_test.tbl_test (key, value) VALUES ('a', 'alpha')",
"GRANT SELECT ON KEYSPACE bridge_table_token_test TO read_only_user",
})
public class BridgeAuthorizationTest extends BridgeIntegrationTest {
private final String keyspaceName = "bridge_table_token_test";
private final String tableName = "tbl_test";
private final String readOnlyUsername = "read_only_user";
private final String readOnlyPassword = "read_only_user";
private final String noAccessUsername = "not_even_reads_user";
private final String noAccessPassword = "tiger";
private static final ObjectMapper objectMapper = new ObjectMapper();
private String authUrlBase;
@BeforeEach
public void perTestSetup(StargateConnectionInfo cluster) {
authUrlBase =
"http://" + cluster.seedAddress() + ":" + 8081; // TODO: make auth port configurable?
}
@SuppressWarnings("unused") // referenced in @StargateSpec
public static void buildParameters(StargateParameters.Builder builder) {
builder.enableAuth(true);
builder.putSystemProperties("stargate.auth_id", "AuthTableBasedService");
}
@Test
public void createKeyspaceCheckAuthorization() throws IOException {
final String keyspace = "ks_bridgeAuthnzTest_CreateKS";
final String createKeyspaceCQL =
String.format(
"CREATE KEYSPACE %s WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':'1'}",
keyspace);
// First: fail if not authenticated (null token)
assertThatThrownBy(
() -> {
stubWithCallCredentials("not-a-token-that-exists")
.executeQuery(
QueryOuterClass.Query.newBuilder().setCql(createKeyspaceCQL).build());
})
.isInstanceOf(StatusRuntimeException.class)
.hasMessageContaining("UNAUTHENTICATED")
.hasMessageContaining("Invalid token");
// Second: also fail if authenticated but not authorized
final String readOnlyToken = generateReadOnlyToken();
assertThatThrownBy(
() -> {
stubWithCallCredentials(readOnlyToken)
.executeQuery(
QueryOuterClass.Query.newBuilder().setCql(createKeyspaceCQL).build());
})
.isInstanceOf(StatusRuntimeException.class)
.hasMessageContaining("PERMISSION_DENIED")
.hasMessageContaining("has no CREATE permission");
// But succeed for Admin user
final String adminToken = generateAdminToken();
QueryOuterClass.Response response =
stubWithCallCredentials(adminToken)
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(createKeyspaceCQL).build());
assertThat(response).isNotNull();
// not 100% sure if anything is expected; seems like an empty ResultSet
assertThat(response.getResultSet()).isNotNull();
}
@Test
public void createTableCheckAuthorization() throws IOException {
final String createTableCQL =
String.format(
"CREATE TABLE bridge_table_token_test.%s (key text PRIMARY KEY, value text)",
"test_table_to_create");
// First: fail if not authenticated (null token)
assertThatThrownBy(
() -> {
stubWithCallCredentials("not-a-token-that-exists")
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(createTableCQL).build());
})
.isInstanceOf(StatusRuntimeException.class)
.hasMessageContaining("UNAUTHENTICATED")
.hasMessageContaining("Invalid token");
// Second: also fail if authenticated but not authorized
final String readOnlyToken = generateReadOnlyToken();
assertThatThrownBy(
() -> {
stubWithCallCredentials(readOnlyToken)
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(createTableCQL).build());
})
.isInstanceOf(StatusRuntimeException.class)
.hasMessageContaining("PERMISSION_DENIED")
.hasMessageContaining("has no CREATE permission");
// But succeed for Admin user
final String adminToken = generateAdminToken();
QueryOuterClass.Response response =
stubWithCallCredentials(adminToken)
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(createTableCQL).build());
assertThat(response).isNotNull();
// not 100% sure if anything is expected; seems like an empty ResultSet
assertThat(response.getResultSet()).isNotNull();
}
@Test
public void selectFromTableCheckAuthorization() throws IOException {
final String readRowCQL = String.format("SELECT * FROM %s.%s", keyspaceName, tableName);
// First, read by authorized read-only user; should find 1 row
final String adminToken = generateReadOnlyToken();
QueryOuterClass.Response response =
stubWithCallCredentials(adminToken)
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(readRowCQL).build());
assertThat(response).isNotNull();
assertThat(response.getResultSet()).isNotNull();
assertThat(response.getResultSet().getRowsCount()).isEqualTo(1);
// And then attempt by another user with no read access
final String readOnlyToken = generateNoAccessToken();
assertThatThrownBy(
() -> {
stubWithCallCredentials(readOnlyToken)
.executeQuery(QueryOuterClass.Query.newBuilder().setCql(readRowCQL).build());
})
.isInstanceOf(StatusRuntimeException.class)
.hasMessageContaining("PERMISSION_DENIED")
.hasMessageContaining("has no SELECT permission");
}
@Test
public void getSupportedFeaturesWithoutAuthentication() {
Schema.SupportedFeaturesResponse supportedFeatures =
stub.getSupportedFeatures(Schema.SupportedFeaturesRequest.newBuilder().build());
// No need to check every field (the values vary across persistence backends). The main object
// of the test is that the call doesn't throw an authentication exception.
assertThat(supportedFeatures.getLoggedBatches()).isTrue();
}
@Test
public void schemaReads() throws IOException {
final String keyspace = "ks_bridgeAuthnzTest_CreateKS";
Schema.SchemaRead schemaRead =
Schema.SchemaRead.newBuilder()
.setElementName(StringValue.newBuilder().setValue(keyspace).build())
.setElementType(Schema.SchemaRead.ElementType.KEYSPACE)
.build();
Schema.AuthorizeSchemaReadsRequest request =
Schema.AuthorizeSchemaReadsRequest.newBuilder().addSchemaReads(schemaRead).build();
// But succeed for Admin user
final String adminToken = generateAdminToken();
Schema.AuthorizeSchemaReadsResponse response =
stubWithCallCredentials(adminToken).authorizeSchemaReads(request);
assertThat(response.getAuthorizedList()).containsOnly(Boolean.TRUE);
}
private String generateNoAccessToken() throws IOException {
return generateAuthToken(authUrlBase, noAccessUsername, noAccessPassword);
}
private String generateReadOnlyToken() throws IOException {
return generateAuthToken(authUrlBase, readOnlyUsername, readOnlyPassword);
}
private String generateAdminToken() throws IOException {
return generateAuthToken(authUrlBase, "cassandra", "cassandra");
}
}