blob: d33d538723a9b7414de1f969627d85480bb127ef [file] [log] [blame]
/*
* 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.phoenix.end2end;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.AuthUtil;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.QueryUtil;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(Parameterized.class)
public class BasePermissionsIT extends BaseTest {
private static final Log LOG = LogFactory.getLog(BasePermissionsIT.class);
static String SUPERUSER;
static HBaseTestingUtility testUtil;
static final Set<String> PHOENIX_SYSTEM_TABLES = new HashSet<>(Arrays.asList(
"SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS", "SYSTEM.FUNCTION"));
static final Set<String> PHOENIX_SYSTEM_TABLES_IDENTIFIERS = new HashSet<>(Arrays.asList(
"SYSTEM.\"CATALOG\"", "SYSTEM.\"SEQUENCE\"", "SYSTEM.\"STATS\"", "SYSTEM.\"FUNCTION\""));
static final String SYSTEM_SEQUENCE_IDENTIFIER =
QueryConstants.SYSTEM_SCHEMA_NAME + "." + "\"" + PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_TABLE+ "\"";
static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES = new HashSet<>(Arrays.asList(
"SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS", "SYSTEM:FUNCTION"));
// Create Multiple users so that we can use Hadoop UGI to run tasks as various users
// Permissions can be granted or revoke by superusers and admins only
// DON'T USE HADOOP UserGroupInformation class to create testing users since HBase misses some of its functionality
// Instead use org.apache.hadoop.hbase.security.User class for testing purposes.
// Super User has all the access
User superUser1 = null;
User superUser2 = null;
// Regular users are granted and revoked permissions as needed
User regularUser1 = null;
User regularUser2 = null;
User regularUser3 = null;
User regularUser4 = null;
// Group User is equivalent of regular user but inside a group
// Permissions can be granted to group should affect this user
static final String GROUP_SYSTEM_ACCESS = "group_system_access";
User groupUser = null;
// Unpriviledged User doesn't have any access and is denied for every action
User unprivilegedUser = null;
static final int NUM_RECORDS = 5;
boolean isNamespaceMapped;
public BasePermissionsIT(final boolean isNamespaceMapped) throws Exception {
this.isNamespaceMapped = isNamespaceMapped;
}
@BeforeClass
public static void doSetup() throws Exception {
SUPERUSER = System.getProperty("user.name");
}
void startNewMiniCluster() throws Exception {
startNewMiniCluster(new Configuration());
}
void startNewMiniCluster(Configuration overrideConf) throws Exception{
if (null != testUtil) {
testUtil.shutdownMiniCluster();
testUtil = null;
}
testUtil = new HBaseTestingUtility();
Configuration config = testUtil.getConfiguration();
enablePhoenixHBaseAuthorization(config);
configureNamespacesOnServer(config);
configureRandomHMasterPort(config);
if (overrideConf != null) {
config.addResource(overrideConf);
}
testUtil.startMiniCluster(1);
initializeUsers(testUtil.getConfiguration());
}
private void initializeUsers(Configuration configuration) {
superUser1 = User.createUserForTesting(configuration, SUPERUSER, new String[0]);
superUser2 = User.createUserForTesting(configuration, "superUser2", new String[0]);
regularUser1 = User.createUserForTesting(configuration, "regularUser1", new String[0]);
regularUser2 = User.createUserForTesting(configuration, "regularUser2", new String[0]);
regularUser3 = User.createUserForTesting(configuration, "regularUser3", new String[0]);
regularUser4 = User.createUserForTesting(configuration, "regularUser4", new String[0]);
groupUser = User.createUserForTesting(testUtil.getConfiguration(), "groupUser", new String[] {GROUP_SYSTEM_ACCESS});
unprivilegedUser = User.createUserForTesting(configuration, "unprivilegedUser", new String[0]);
}
private void configureRandomHMasterPort(Configuration config) {
// Avoid multiple clusters trying to bind the master's info port (16010)
config.setInt(HConstants.MASTER_INFO_PORT, -1);
}
void enablePhoenixHBaseAuthorization(Configuration config) {
config.set("hbase.superuser", SUPERUSER + "," + "superUser2");
config.set("hbase.security.authorization", Boolean.TRUE.toString());
config.set("hbase.security.exec.permission.checks", Boolean.TRUE.toString());
config.set("hbase.coprocessor.master.classes",
"org.apache.hadoop.hbase.security.access.AccessController");
config.set("hbase.coprocessor.region.classes",
"org.apache.hadoop.hbase.security.access.AccessController");
config.set("hbase.coprocessor.regionserver.classes",
"org.apache.hadoop.hbase.security.access.AccessController");
config.set(QueryServices.PHOENIX_ACLS_ENABLED,"true");
config.set("hbase.regionserver.wal.codec", "org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec");
}
void configureNamespacesOnServer(Configuration conf) {
conf.set(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(isNamespaceMapped));
}
@Parameterized.Parameters(name = "isNamespaceMapped={0}") // name is used by failsafe as file name in reports
public static Collection<Boolean> data() {
return Arrays.asList(false, true);
}
@After
public void cleanup() throws Exception {
if (testUtil != null) {
testUtil.shutdownMiniCluster();
testUtil = null;
}
}
public static HBaseTestingUtility getUtility(){
return testUtil;
}
// Utility functions to grant permissions with HBase API
void grantPermissions(String toUser, Set<String> tablesToGrant, Permission.Action... actions) throws Throwable {
for (String table : tablesToGrant) {
AccessControlClient.grant(getUtility().getConnection(), TableName.valueOf(table), toUser, null, null,
actions);
}
}
void grantPermissions(String toUser, String namespace, Permission.Action... actions) throws Throwable {
AccessControlClient.grant(getUtility().getConnection(), namespace, toUser, actions);
}
void grantPermissions(String groupEntry, Permission.Action... actions) throws IOException, Throwable {
AccessControlClient.grant(getUtility().getConnection(), groupEntry, actions);
}
// Utility functions to revoke permissions with HBase API
void revokeAll() throws Throwable {
AccessControlClient.revoke(getUtility().getConnection(), AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS), Permission.Action.values() );
AccessControlClient.revoke(getUtility().getConnection(), regularUser1.getShortName(), Permission.Action.values() );
AccessControlClient.revoke(getUtility().getConnection(), unprivilegedUser.getShortName(), Permission.Action.values() );
}
Properties getClientProperties(String tenantId) {
Properties props = new Properties();
if(tenantId != null) {
props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId);
}
props.setProperty(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(isNamespaceMapped));
return props;
}
public Connection getConnection() throws SQLException {
return getConnection(null);
}
public Connection getConnection(String tenantId) throws SQLException {
return DriverManager.getConnection(getUrl(), getClientProperties(tenantId));
}
protected static String getUrl() {
return "jdbc:phoenix:localhost:" + testUtil.getZkCluster().getClientPort() + ":/hbase";
}
static Set<String> getHBaseTables() throws IOException {
Set<String> tables = new HashSet<>();
for (TableName tn : testUtil.getHBaseAdmin().listTableNames()) {
tables.add(tn.getNameAsString());
}
return tables;
}
// UG Object
// 1. Instance of String --> represents GROUP name
// 2. Instance of User --> represents HBase user
AccessTestAction grantPermissions(final String actions, final Object ug,
final String tableOrSchemaList, final boolean isSchema) throws SQLException {
return grantPermissions(actions, ug, Collections.singleton(tableOrSchemaList), isSchema);
}
AccessTestAction grantPermissions(final String actions, final Object ug,
final Set<String> tableOrSchemaList, final boolean isSchema) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
for(String tableOrSchema : tableOrSchemaList) {
String grantStmtSQL = "GRANT '" + actions + "' ON " + (isSchema ? " SCHEMA " : " TABLE ") + tableOrSchema + " TO "
+ ((ug instanceof String) ? (" GROUP " + "'" + ug + "'") : ("'" + ((User)ug).getShortName() + "'"));
LOG.info("Grant Permissions SQL: " + grantStmtSQL);
assertFalse(stmt.execute(grantStmtSQL));
}
}
return null;
}
};
}
AccessTestAction grantPermissions(final String actions, final User user) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
String grantStmtSQL = "GRANT '" + actions + "' TO " + " '" + user.getShortName() + "'";
LOG.info("Grant Permissions SQL: " + grantStmtSQL);
assertFalse(stmt.execute(grantStmtSQL));
}
return null;
}
};
}
AccessTestAction revokePermissions(final Object ug,
final String tableOrSchemaList, final boolean isSchema) throws SQLException {
return revokePermissions(ug, Collections.singleton(tableOrSchemaList), isSchema);
}
AccessTestAction revokePermissions(final Object ug,
final Set<String> tableOrSchemaList, final boolean isSchema) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
for(String tableOrSchema : tableOrSchemaList) {
String revokeStmtSQL = "REVOKE ON " + (isSchema ? " SCHEMA " : " TABLE ") + tableOrSchema + " FROM "
+ ((ug instanceof String) ? (" GROUP " + "'" + ug + "'") : ("'" + ((User)ug).getShortName() + "'"));
LOG.info("Revoke Permissions SQL: " + revokeStmtSQL);
assertFalse(stmt.execute(revokeStmtSQL));
}
}
return null;
}
};
}
AccessTestAction revokePermissions(final Object ug) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
String revokeStmtSQL = "REVOKE FROM " +
((ug instanceof String) ? (" GROUP " + "'" + ug + "'") : ("'" + ((User)ug).getShortName() + "'"));
LOG.info("Revoke Permissions SQL: " + revokeStmtSQL);
assertFalse(stmt.execute(revokeStmtSQL));
}
return null;
}
};
}
// Attempts to get a Phoenix Connection
// New connections could create SYSTEM tables if appropriate perms are granted
AccessTestAction getConnectionAction() throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection();) {
}
return null;
}
};
}
AccessTestAction createSchema(final String schemaName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
if (isNamespaceMapped) {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("CREATE SCHEMA " + schemaName));
}
}
return null;
}
};
}
AccessTestAction dropSchema(final String schemaName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
if (isNamespaceMapped) {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("DROP SCHEMA " + schemaName));
}
}
return null;
}
};
}
AccessTestAction createTable(final String tableName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk INTEGER not null primary key, data VARCHAR, val integer)"));
try (PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?, ?, ?)")) {
for (int i = 0; i < NUM_RECORDS; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, Integer.toString(i));
pstmt.setInt(3, i);
assertEquals(1, pstmt.executeUpdate());
}
}
conn.commit();
}
return null;
}
};
}
AccessTestAction createMultiTenantTable(final String tableName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("CREATE TABLE " + tableName
+ "(ORG_ID VARCHAR NOT NULL, PREFIX CHAR(3) NOT NULL, DATA VARCHAR, VAL INTEGER CONSTRAINT PK PRIMARY KEY (ORG_ID, PREFIX)) MULTI_TENANT=TRUE"));
try (PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?, ?, ?, ?)")) {
for (int i = 0; i < NUM_RECORDS; i++) {
pstmt.setString(1, "o" + i);
pstmt.setString(2, "pr" + i);
pstmt.setString(3, Integer.toString(i));
pstmt.setInt(4, i);
assertEquals(1, pstmt.executeUpdate());
}
}
conn.commit();
}
return null;
}
};
}
AccessTestAction dropTable(final String tableName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
}
return null;
}
};
}
// Attempts to read given table without verifying data
// AccessDeniedException is only triggered when ResultSet#next() method is called
// The first call triggers HBase Scan object
// The Statement#executeQuery() method returns an iterator and doesn't interact with HBase API at all
AccessTestAction readTableWithoutVerification(final String tableName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(rs);
while (rs.next()) {
}
}
return null;
}
};
}
AccessTestAction readTable(final String tableName) throws SQLException {
return readTable(tableName,null);
}
AccessTestAction readTable(final String tableName, final String indexName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
String readTableSQL = "SELECT "+(indexName!=null?"/*+ INDEX("+tableName+" "+indexName+")*/":"")+" pk, data, val FROM " + tableName +" where data >= '0'";
ResultSet rs = stmt.executeQuery(readTableSQL);
assertNotNull(rs);
int i = 0;
while (rs.next()) {
assertEquals(i, rs.getInt(1));
assertEquals(Integer.toString(i), rs.getString(2));
assertEquals(i, rs.getInt(3));
i++;
}
assertEquals(NUM_RECORDS, i);
}
return null;
}
};
}
AccessTestAction readMultiTenantTableWithoutIndex(final String tableName) throws SQLException {
return readMultiTenantTableWithoutIndex(tableName, null);
}
AccessTestAction readMultiTenantTableWithoutIndex(final String tableName, final String tenantId) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(tenantId); Statement stmt = conn.createStatement()) {
// Accessing all the data from the table avoids the use of index
String readTableSQL = "SELECT data, val FROM " + tableName;
ResultSet rs = stmt.executeQuery(readTableSQL);
assertNotNull(rs);
int i = 0;
String explainPlan = Joiner.on(" ").join(((PhoenixStatement)stmt).getQueryPlan().getExplainPlan().getPlanSteps());
rs = stmt.executeQuery(readTableSQL);
if(tenantId != null) {
rs.next();
assertFalse(explainPlan.contains("_IDX_"));
assertEquals(((PhoenixConnection)conn).getTenantId().toString(), tenantId);
// For tenant ID "o3", the value in table will be 3
assertEquals(Character.toString(tenantId.charAt(1)), rs.getString(1));
// Only 1 record is inserted per Tenant
assertFalse(rs.next());
} else {
while(rs.next()) {
assertEquals(Integer.toString(i), rs.getString(1));
assertEquals(i, rs.getInt(2));
i++;
}
assertEquals(NUM_RECORDS, i);
}
}
return null;
}
};
}
AccessTestAction readMultiTenantTableWithIndex(final String tableName) throws SQLException {
return readMultiTenantTableWithIndex(tableName, null);
}
AccessTestAction readMultiTenantTableWithIndex(final String tableName, final String tenantId) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(tenantId); Statement stmt = conn.createStatement()) {
// Accessing only the 'data' from the table uses index since index tables are built on 'data' column
String readTableSQL = "SELECT data FROM " + tableName;
ResultSet rs = stmt.executeQuery(readTableSQL);
assertNotNull(rs);
int i = 0;
String explainPlan = Joiner.on(" ").join(((PhoenixStatement) stmt).getQueryPlan().getExplainPlan().getPlanSteps());
assertTrue(explainPlan.contains("_IDX_"));
rs = stmt.executeQuery(readTableSQL);
if (tenantId != null) {
rs.next();
assertEquals(((PhoenixConnection) conn).getTenantId().toString(), tenantId);
// For tenant ID "o3", the value in table will be 3
assertEquals(Character.toString(tenantId.charAt(1)), rs.getString(1));
// Only 1 record is inserted per Tenant
assertFalse(rs.next());
} else {
while (rs.next()) {
assertEquals(Integer.toString(i), rs.getString(1));
i++;
}
assertEquals(NUM_RECORDS, i);
}
}
return null;
}
};
}
AccessTestAction addProperties(final String tableName, final String property, final String value)
throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("ALTER TABLE " + tableName + " SET " + property + "=" + value));
}
return null;
}
};
}
AccessTestAction addColumn(final String tableName, final String columnName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("ALTER TABLE " + tableName + " ADD "+columnName+" varchar"));
}
return null;
}
};
}
AccessTestAction dropColumn(final String tableName, final String columnName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("ALTER TABLE " + tableName + " DROP COLUMN "+columnName));
}
return null;
}
};
}
AccessTestAction createIndex(final String indexName, final String dataTable) throws SQLException {
return createIndex(indexName, dataTable, null);
}
AccessTestAction createIndex(final String indexName, final String dataTable, final String tenantId) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(tenantId); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("CREATE INDEX " + indexName + " on " + dataTable + "(data)"));
}
return null;
}
};
}
AccessTestAction createLocalIndex(final String indexName, final String dataTable) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("CREATE LOCAL INDEX " + indexName + " on " + dataTable + "(data)"));
}
return null;
}
};
}
AccessTestAction dropIndex(final String indexName, final String dataTable) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("DROP INDEX " + indexName + " on " + dataTable));
}
return null;
}
};
}
AccessTestAction rebuildIndex(final String indexName, final String dataTable) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("ALTER INDEX " + indexName + " on " + dataTable + " DISABLE"));
assertFalse(stmt.execute("ALTER INDEX " + indexName + " on " + dataTable + " REBUILD"));
}
return null;
}
};
}
AccessTestAction dropView(final String viewName) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) {
assertFalse(stmt.execute("DROP VIEW " + viewName));
}
return null;
}
};
}
AccessTestAction createView(final String viewName, final String dataTable) throws SQLException {
return createView(viewName, dataTable, null);
}
AccessTestAction createView(final String viewName, final String dataTable, final String tenantId) throws SQLException {
return new AccessTestAction() {
@Override
public Object run() throws Exception {
try (Connection conn = getConnection(tenantId); Statement stmt = conn.createStatement();) {
String viewStmtSQL = "CREATE VIEW " + viewName + " AS SELECT * FROM " + dataTable;
assertFalse(stmt.execute(viewStmtSQL));
}
return null;
}
};
}
static interface AccessTestAction extends PrivilegedExceptionAction<Object> { }
/** This fails only in case of ADE or empty list for any of the users. */
void verifyAllowed(AccessTestAction action, User... users) throws Exception {
if(users.length == 0) {
throw new Exception("Action needs at least one user to run");
}
for (User user : users) {
verifyAllowed(user, action);
}
}
void verifyAllowed(User user, TableDDLPermissionsIT.AccessTestAction... actions) throws Exception {
for (TableDDLPermissionsIT.AccessTestAction action : actions) {
try {
Object obj = user.runAs(action);
if (obj != null && obj instanceof List<?>) {
List<?> results = (List<?>) obj;
if (results != null && results.isEmpty()) {
fail("Empty non null results from action for user '" + user.getShortName() + "'");
}
}
} catch (AccessDeniedException ade) {
fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
}
}
}
/** This passes only if desired exception is caught for all users. */
<T> void verifyDenied(AccessTestAction action, Class<T> exception, User... users) throws Exception {
if(users.length == 0) {
throw new Exception("Action needs at least one user to run");
}
for (User user : users) {
verifyDenied(user, exception, action);
}
}
/** This passes only if desired exception is caught for all users. */
<T> void verifyDenied(User user, Class<T> exception, TableDDLPermissionsIT.AccessTestAction... actions) throws Exception {
for (TableDDLPermissionsIT.AccessTestAction action : actions) {
try {
user.runAs(action);
fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
} catch (IOException e) {
fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
} catch (UndeclaredThrowableException ute) {
Throwable ex = ute.getUndeclaredThrowable();
// HBase AccessDeniedException(ADE) is handled in different ways in different parts of code
// 1. Wrap HBase ADE in PhoenixIOException (Mostly for create, delete statements)
// 2. Wrap HBase ADE in ExecutionException (Mostly for scans)
// 3. Directly throwing HBase ADE or custom msg with HBase ADE
// Thus we iterate over the chain of throwables and find ADE
for(Throwable throwable : Throwables.getCausalChain(ex)) {
if(exception.equals(throwable.getClass())) {
if(throwable instanceof AccessDeniedException) {
validateAccessDeniedException((AccessDeniedException) throwable);
}
return;
}
}
} catch(RuntimeException ex) {
// This can occur while accessing tabledescriptors from client by the unprivileged user
if (ex.getCause() instanceof AccessDeniedException) {
// expected result
validateAccessDeniedException((AccessDeniedException) ex.getCause());
return;
}
}
fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
}
}
String surroundWithDoubleQuotes(String input) {
return "\"" + input + "\"";
}
void validateAccessDeniedException(AccessDeniedException ade) {
String msg = ade.getMessage();
assertTrue("Exception contained unexpected message: '" + msg + "'",
!msg.contains("is not the scanner owner"));
}
}