blob: c065f7f87fadfe250249629ada9085bf71391ace [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.sentry.tests.e2e.dbprovider;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import org.apache.hive.service.cli.HiveSQLException;
import org.apache.sentry.tests.e2e.hive.AbstractTestWithStaticConfiguration;
import org.apache.sentry.tests.e2e.hive.PrivilegeResultSet;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Contains tests for meta data operations with column level privileges
*/
public class TestDbColumnLevelMetaDataOps extends AbstractTestWithStaticConfiguration {
private static final Logger LOGGER = LoggerFactory.
getLogger(TestDbColumnLevelMetaDataOps.class);
private static final String TEST_COL_METADATA_OPS_DB = "test_col_metadata_ops_db";
private static final String TEST_COL_METADATA_OPS_TB = "test_col_metadata_ops_tb";
private static final String TEST_COL_METADATA_OPS_ROLE = "test_col_metadata_ops_role";
@BeforeClass
public static void setupTestStaticConfiguration() throws Exception{
LOGGER.info("TestColumnEndToEnd setupTestStaticConfiguration");
useSentryService = true;
AbstractTestWithStaticConfiguration.setupTestStaticConfiguration();
}
@Override
@Before
public void setup() throws Exception {
super.setupAdmin();
super.setup();
createTestData();
}
private static Statement statement = null;
private static Connection connection = null;
private static void establishSession(String user) throws Exception{
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
connection = context.createConnection(user);
statement = context.createStatement(connection);
}
/**
* Create test database, table and role
* and grant column level privilege
* @throws Exception
*/
private void createTestData() throws Exception {
establishSession(ADMIN1);
statement.execute("CREATE DATABASE " + TEST_COL_METADATA_OPS_DB);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
statement.execute("CREATE TABLE " + TEST_COL_METADATA_OPS_TB
+ " (privileged STRING, unprivileged INT) partitioned by (privileged_par STRING, unprivileged_par INT)");
statement.execute("INSERT INTO TABLE " + TEST_COL_METADATA_OPS_TB
+ " PARTITION(privileged_par = 'privileged_par', unprivileged_par = 1) VALUES ('test1', 1)");
statement.execute("CREATE ROLE " + TEST_COL_METADATA_OPS_ROLE);
statement.execute("GRANT SELECT(privileged) ON TABLE " + TEST_COL_METADATA_OPS_TB + " TO ROLE " + TEST_COL_METADATA_OPS_ROLE);
statement.execute("GRANT ROLE " + TEST_COL_METADATA_OPS_ROLE + " TO GROUP " + USERGROUP1);
PrivilegeResultSet prset = new PrivilegeResultSet(statement, "SHOW GRANT ROLE "
+ TEST_COL_METADATA_OPS_ROLE + " ON DATABASE " + TEST_COL_METADATA_OPS_DB);
LOGGER.info("SHOW GRANT : " + prset.toString());
prset.verifyResultSetColumn("table", TEST_COL_METADATA_OPS_TB);
prset.verifyResultSetColumn("column", "privileged");
prset.verifyResultSetColumn("privilege", "select");
}
private ResultSet executeQueryWithLog(String query) throws Exception {
ResultSet rs;
try {
LOGGER.info("Running " + query);
rs = statement.executeQuery(query);
return rs;
} catch (HiveSQLException ex) {
LOGGER.error("Privilege exception occurs when running : " + query);
throw ex;
}
}
private void validateFiltersInaccessibleColumns(String query, String colMetaField, String user,
String privileged) throws Exception {
establishSession(user);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
ResultSet rs = executeQueryWithLog(query);
int numColumns = 0;
while (rs.next()) {
String val = rs.getString(colMetaField);
numColumns++;
// Relax validation for now:
// user with any select privilege can perform metadata operations,
// even though it might show some columns which he doesn't have privileges
assertTrue("Can access non privileged column", val.equalsIgnoreCase(privileged));
}
rs.close();
assertTrue("Looks like we accessed more columns than needed", numColumns == 1);
}
private void validateShowsAllColumns(String query, String colMetaField, String user,
String privileged, String unprivileged) throws Exception {
establishSession(user);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
ResultSet rs = executeQueryWithLog(query);
boolean found = false;
while (rs.next()) {
String val = rs.getString(colMetaField);
// Relax validation for now:
// user with any select privilege can perform metadata operations,
// even though it might show some columns which he doesn't have privileges
//assertFalse("column unprivileged shouldn't be shown in result",
// val.equalsIgnoreCase("unprivileged"));
if (val.equalsIgnoreCase("unprivileged")) {
LOGGER.warn("column unprivileged related metadata info is not disabled from result");
}
if (val.toLowerCase().contains(privileged)) {
LOGGER.info("detected privileged column information: " + privileged);
found = true;
} else if (val.toLowerCase().contains(unprivileged)) {
LOGGER.warn("detected unexpected column information: " + unprivileged);
}
}
rs.close();
assertTrue("failed to detect column privileged from result", found);
}
private void validateShowsAllColumns(String query, String colMetaField, String user) throws Exception {
validateShowsAllColumns(query, colMetaField, user, "privileged", "unprivileged");
}
private void validateSemanticException(String query, String user) throws Exception {
establishSession(user);
try {
LOGGER.info("Running " + query);
statement.execute(query);
fail("failed to throw SemanticException");
} catch (Exception ex) {
String err = "SemanticException No valid privileges";
assertTrue("failed to detect " + err + "\n" + ex.getMessage(),
ex.getMessage().contains("SemanticException No valid privileges"));
}
}
/**
* Test with column level privilege
* user can NOT "show table extended"
*/
@Test
public void testShowExtended() throws Exception {
String query = "SHOW TABLE EXTENDED IN " + TEST_COL_METADATA_OPS_DB
+ " like '" + TEST_COL_METADATA_OPS_TB + "'";
// with column level privileges, user can not do show extended
validateSemanticException(query, USER1_1);
// negative test, without any privileges, user can not do it also
validateSemanticException(query, USER2_1);
}
/**
* Test with column level privileges,
* user can list all columns for now
*/
@Test
public void testShowColumns() throws Exception {
String query = "SHOW COLUMNS IN " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
// with column level privileges, user can show columns
validateFiltersInaccessibleColumns(query, "field", USER1_1, "privileged");
// without column/table level privileges, any user can NOT show columns
validateSemanticException(query, USER2_1);
}
/**
* Test SHOW TBLPROPERTIES requires table level privileges
* @throws Exception
*/
@Test
public void testShowProperties() throws Exception {
String query = "SHOW TBLPROPERTIES " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
}
/**
* Test with column level select privilege,
* user can do "describe table"
*/
@Test
public void testDescribeTable() throws Exception {
String query = "DESCRIBE " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
// with column level privilege, user can describe table, but columns are not filtered for now
validateShowsAllColumns(query, "col_name", USER1_1);
// without column/table level privileges, any user can NOT describe table
validateSemanticException(query, USER2_1);
// only with table level privileges user can describe extended/formatted
query = "DESCRIBE EXTENDED " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "DESCRIBE EXTENDED " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB + " s";
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "DESCRIBE FORMATTED " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "DESCRIBE FORMATTED " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB + " s";
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
}
/**
* Test with column level select privilege,
* user can only do "explain select column";
* any other select requires table level privileges
* @throws Exception
*/
@Ignore("After fix SENTRY-849, should enable this test")
@Test
public void testExplainSelect() throws Exception {
String query = "EXPLAIN SELECT privileged FROM " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
// With column level privilege, user can explain select column
validateShowsAllColumns(query, "Explain", USER1_1);
// Without column/table level privilege, user can NOT explain select column
validateSemanticException(query, USER2_1);
// user can NOT explain select unprivileged column
query = "EXPLAIN SELECT unprivileged FROM " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "EXPLAIN SELECT * FROM " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "EXPLAIN SELECT count(*) FROM " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
query = "EXPLAIN SELECT * FROM (SELECT privileged AS c FROM " +
TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB + " union all select unprivileged as c from "
+ TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB + ") subq1 order by c";
validateSemanticException(query, USER1_1);
validateSemanticException(query, USER2_1);
}
/**
* Test if add a new column and grant privilege,
* user1 needs explicit grant on new column to access this column
*/
@Test
public void testShowNewColumn() throws Exception {
String colName = "newcol";
establishSession(ADMIN1);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
statement.execute("ALTER TABLE " + TEST_COL_METADATA_OPS_TB + " ADD COLUMNS (" + colName + " STRING)");
String query = "SHOW COLUMNS IN " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
establishSession(USER1_1);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
ResultSet rs = executeQueryWithLog(query);
boolean found = false;
while (rs.next() && !found) {
String val = rs.getString("field");
LOGGER.info("found " + val);
if (val.equalsIgnoreCase(colName)) {
found = true;
}
}
assertTrue("Should not have implicit access to new column " + colName, !found);
rs.close();
establishSession(ADMIN1);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
statement.execute("GRANT SELECT(" + colName + ") ON TABLE " + TEST_COL_METADATA_OPS_TB + " TO ROLE " + TEST_COL_METADATA_OPS_ROLE);
establishSession(USER1_1);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
rs = executeQueryWithLog(query);
found = false;
while (rs.next() && !found) {
String val = rs.getString("field");
LOGGER.info("found " + val);
if (val.equalsIgnoreCase(colName)) {
found = true;
}
}
assertTrue("Should have implicit access to new column " + colName, found);
rs.close();
validateSemanticException(query, USER2_1);
}
/**
* Grant user column level privileges, show partitions
* should list user's granted columns
* @throws Exception
*/
@Ignore("After fix SENTRY-898, turn on this test")
@Test
public void testShowPartitions() throws Exception {
final String PAR_ROLE_NAME = TEST_COL_METADATA_OPS_ROLE + "_2";
establishSession(ADMIN1);
statement.execute("USE " + TEST_COL_METADATA_OPS_DB);
statement.execute("CREATE ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT SELECT(privileged_par) ON TABLE " + TEST_COL_METADATA_OPS_TB + " TO ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ROLE " + PAR_ROLE_NAME + " TO GROUP " + USERGROUP1);
String query = "SHOW PARTITIONS " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateFiltersInaccessibleColumns(query, "partition", USER1_1, "privileged_par");
}
/**
* Requires table level privileges
*/
@Test
public void testShowTblProperties() throws Exception {
String query = "SHOW TBLPROPERTIES " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
}
/**
* Requires table level privileges
*/
@Test
public void testShowCreateTable() throws Exception {
String query = "SHOW CREATE TABLE " + TEST_COL_METADATA_OPS_DB + "." + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
}
/**
* Requires table level privileges
*/
@Test
public void testTableExtendLike() throws Exception {
String query = "SHOW TABLE EXTENDED IN " + TEST_COL_METADATA_OPS_DB + " LIKE " + TEST_COL_METADATA_OPS_TB;
validateSemanticException(query, USER1_1);
}
/**
* User cannot exchange partition of tables without any privilege on input table and output table
* @throws Exception
*/
@Test
public void testAlterTableExchangeNoPrivilege() throws Exception {
final String PAR_ROLE_NAME = "config1_user_role";
final String PAR_GROUP_NAME = USERGROUP1;
final String PAR_DB_NAME = "config1_test_database1";
final String PAR_INPUT_TABLE_NAME = "aliens";
final String PAR_OUTPUT_TABLE_NAME = "movie_stars";
establishSession(ADMIN1);
statement.execute("DROP DATABASE IF EXISTS " + PAR_DB_NAME + " CASCADE");
statement.execute("CREATE DATABASE " + PAR_DB_NAME);
statement.execute("CREATE ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ROLE " + PAR_ROLE_NAME + " TO GROUP " + PAR_GROUP_NAME);
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='earth', diet='milk shakes')");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='trapis-4', diet='sentient lifeforms with cheese')");
String query = "ALTER TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " EXCHANGE PARTITION (home_planet='earth', diet='milk shakes') WITH TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME;
validateSemanticException(query, USER1_1);
}
/**
* User cannot exchange partition of tables without input required privilege
* @throws Exception
*/
@Test
public void testAlterTableExchangeNoPrivilegeOnInput() throws Exception {
final String PAR_ROLE_NAME = "config1_user_role";
final String PAR_GROUP_NAME = USERGROUP1;
final String PAR_DB_NAME = "config1_test_database2";
final String PAR_INPUT_TABLE_NAME = "aliens";
final String PAR_OUTPUT_TABLE_NAME = "movie_stars";
establishSession(ADMIN1);
statement.execute("DROP DATABASE IF EXISTS " + PAR_DB_NAME + " CASCADE");
statement.execute("CREATE DATABASE " + PAR_DB_NAME);
statement.execute("CREATE ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ROLE " + PAR_ROLE_NAME + " TO GROUP " + PAR_GROUP_NAME);
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='earth', diet='milk shakes')");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='trapis-4', diet='sentient lifeforms with cheese')");
// grant propert privilege to output table
statement.execute("GRANT INSERT ON TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ALTER ON TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
// move a partition from a source table to target table and alter each table's metadata.
// ALTER TABLE <dest_table> EXCHANGE PARTITION (<[partial] partition spec>) WITH TABLE <src_table>
String query = "ALTER TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " EXCHANGE PARTITION (home_planet='earth', diet='milk shakes') WITH TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME;
validateSemanticException(query, USER1_1);
}
/**
* User cannot exchange partition of tables without output required privilege
* @throws Exception
*/
@Test
public void testAlterTableExchangeNoPrivilegeOnOutput() throws Exception {
final String PAR_ROLE_NAME = "config1_user_role";
final String PAR_GROUP_NAME = USERGROUP1;
final String PAR_DB_NAME = "config1_test_database3";
final String PAR_INPUT_TABLE_NAME = "aliens";
final String PAR_OUTPUT_TABLE_NAME = "movie_stars";
establishSession(ADMIN1);
statement.execute("DROP DATABASE IF EXISTS " + PAR_DB_NAME + " CASCADE");
statement.execute("CREATE DATABASE " + PAR_DB_NAME);
statement.execute("CREATE ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ROLE " + PAR_ROLE_NAME + " TO GROUP " + PAR_GROUP_NAME);
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='earth', diet='milk shakes')");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='trapis-4', diet='sentient lifeforms with cheese')");
// grant propert privilege to input table
statement.execute("GRANT ALL ON TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
// move a partition from a source table to target table and alter each table's metadata.
// ALTER TABLE <dest_table> EXCHANGE PARTITION (<[partial] partition spec>) WITH TABLE <src_table>
String query = "ALTER TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " EXCHANGE PARTITION (home_planet='earth', diet='milk shakes') WITH TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME;
validateSemanticException(query, USER1_1);
}
/**
* User can exchange partition of tables with both input and output required privileges
* @throws Exception
*/
@Test
public void testAlterTableExchangeWithPrivilege() throws Exception {
final String PAR_ROLE_NAME = "config1_user_role";
final String PAR_GROUP_NAME = USERGROUP1;
final String PAR_DB_NAME = "config1_test_database4";
final String PAR_INPUT_TABLE_NAME = "aliens";
final String PAR_OUTPUT_TABLE_NAME = "movie_stars";
establishSession(ADMIN1);
statement.execute("DROP DATABASE IF EXISTS " + PAR_DB_NAME + " CASCADE");
statement.execute("CREATE DATABASE " + PAR_DB_NAME);
statement.execute("CREATE ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ROLE " + PAR_ROLE_NAME + " TO GROUP " + PAR_GROUP_NAME);
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("CREATE TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " (name string) PARTITIONED BY (home_planet string, diet string)");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='earth', diet='milk shakes')");
statement.execute("ALTER TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " ADD PARTITION (home_planet='trapis-4', diet='sentient lifeforms with cheese')");
// grant propert privilege to input table
statement.execute("GRANT ALL ON TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
// grant propert privilege to output table
statement.execute("GRANT INSERT ON TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
statement.execute("GRANT ALTER ON TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " TO ROLE " + PAR_ROLE_NAME);
// move a partition from a source table to target table and alter each table's metadata.
// ALTER TABLE <dest_table> EXCHANGE PARTITION (<[partial] partition spec>) WITH TABLE <src_table>
String query = "ALTER TABLE " + PAR_DB_NAME + "." + PAR_OUTPUT_TABLE_NAME + " EXCHANGE PARTITION (home_planet='earth', diet='milk shakes') WITH TABLE " + PAR_DB_NAME + "." + PAR_INPUT_TABLE_NAME;
establishSession(USER1_1);
statement.execute(query);
}
}