You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by td...@apache.org on 2017/11/30 06:21:02 UTC
[1/2] phoenix git commit: PHOENIX-672 Add GRANT and REVOKE commands
using HBase AccessController
Repository: phoenix
Updated Branches:
refs/heads/master 355ee522c -> 88038a2da
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableDDLPermissionsIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableDDLPermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableDDLPermissionsIT.java
index 971383b..8666bb8 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableDDLPermissionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableDDLPermissionsIT.java
@@ -16,144 +16,53 @@
*/
package org.apache.phoenix.end2end;
-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;
-
-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.Map;
-import java.util.Properties;
-import java.util.Set;
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.NamespaceDescriptor;
-import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission.Action;
-import org.apache.hadoop.hbase.util.Bytes;
-import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.phoenix.exception.PhoenixIOException;
-import org.apache.phoenix.query.QueryServices;
-import org.apache.phoenix.util.MetaDataUtil;
import org.apache.phoenix.util.SchemaUtil;
-import org.junit.After;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import com.google.common.collect.Maps;
/**
* Test that verifies a user can read Phoenix tables with a minimal set of permissions.
*/
@Category(NeedsOwnMiniClusterTest.class)
-@RunWith(Parameterized.class)
-public class TableDDLPermissionsIT{
- private static String SUPERUSER;
-
- private static HBaseTestingUtility testUtil;
-
- private static final Set<String> PHOENIX_SYSTEM_TABLES = new HashSet<>(Arrays.asList(
- "SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS", "SYSTEM.FUNCTION",
- "SYSTEM.MUTEX"));
- // PHOENIX-XXXX SYSTEM.MUTEX isn't being created in the SYSTEM namespace as it should be.
- private static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES = new HashSet<>(
- Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS", "SYSTEM:FUNCTION",
- "SYSTEM.MUTEX"));
- private static final String GROUP_SYSTEM_ACCESS = "group_system_access";
- final UserGroupInformation superUser = UserGroupInformation.createUserForTesting(SUPERUSER, new String[0]);
- final UserGroupInformation superUser2 = UserGroupInformation.createUserForTesting("superuser", new String[0]);
- final UserGroupInformation regularUser = UserGroupInformation.createUserForTesting("user", new String[0]);
- final UserGroupInformation groupUser = UserGroupInformation.createUserForTesting("user2", new String[] { GROUP_SYSTEM_ACCESS });
- final UserGroupInformation unprivilegedUser = UserGroupInformation.createUserForTesting("unprivilegedUser",
- new String[0]);
-
+public class TableDDLPermissionsIT extends BasePermissionsIT {
- private static final int NUM_RECORDS = 5;
-
- private boolean isNamespaceMapped;
-
- public TableDDLPermissionsIT(final boolean isNamespaceMapped) throws Exception {
- this.isNamespaceMapped = isNamespaceMapped;
- Map<String, String> clientProps = Maps.newHashMapWithExpectedSize(1);
- clientProps.put(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "true");
+ public TableDDLPermissionsIT(boolean isNamespaceMapped) throws Exception {
+ super(isNamespaceMapped);
}
- private void startNewMiniCluster(Configuration overrideConf) throws Exception{
- if (null != testUtil) {
- testUtil.shutdownMiniCluster();
- testUtil = null;
- }
- testUtil = new HBaseTestingUtility();
-
- Configuration config = testUtil.getConfiguration();
-
- 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("hbase.security.exec.permission.checks", "true");
- config.set("hbase.security.authorization", "true");
- config.set("hbase.superuser", SUPERUSER+","+superUser2.getShortUserName());
- config.set("hbase.regionserver.wal.codec", "org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec");
- config.set(QueryServices.PHOENIX_ACLS_ENABLED,"true");
- config.set(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(isNamespaceMapped));
- // Avoid multiple clusters trying to bind the master's info port (16010)
- config.setInt(HConstants.MASTER_INFO_PORT, -1);
-
- if (overrideConf != null) {
- config.addResource(overrideConf);
- }
- testUtil.startMiniCluster(1);
- }
-
- private void grantSystemTableAccess() throws Exception{
+ private void grantSystemTableAccess() throws Exception {
try (Connection conn = getConnection()) {
if (isNamespaceMapped) {
- grantPermissions(regularUser.getShortUserName(), PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES, Action.READ,
+ grantPermissions(regularUser1.getShortName(), PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES, Action.READ,
Action.EXEC);
- grantPermissions(unprivilegedUser.getShortUserName(), PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES,
+ grantPermissions(unprivilegedUser.getShortName(), PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES,
Action.READ, Action.EXEC);
grantPermissions(AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS), PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES,
Action.READ, Action.EXEC);
// Local Index requires WRITE permission on SYSTEM.SEQUENCE TABLE.
- grantPermissions(regularUser.getShortUserName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
+ grantPermissions(regularUser1.getShortName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
Action.READ, Action.EXEC);
- grantPermissions(unprivilegedUser.getShortUserName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
+ grantPermissions(unprivilegedUser.getShortName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
Action.READ, Action.EXEC);
} else {
- grantPermissions(regularUser.getShortUserName(), PHOENIX_SYSTEM_TABLES, Action.READ, Action.EXEC);
- grantPermissions(unprivilegedUser.getShortUserName(), PHOENIX_SYSTEM_TABLES, Action.READ, Action.EXEC);
+ grantPermissions(regularUser1.getShortName(), PHOENIX_SYSTEM_TABLES, Action.READ, Action.EXEC);
+ grantPermissions(unprivilegedUser.getShortName(), PHOENIX_SYSTEM_TABLES, Action.READ, Action.EXEC);
grantPermissions(AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS), PHOENIX_SYSTEM_TABLES, Action.READ, Action.EXEC);
// Local Index requires WRITE permission on SYSTEM.SEQUENCE TABLE.
- grantPermissions(regularUser.getShortUserName(), Collections.singleton("SYSTEM.SEQUENCE"), Action.WRITE,
+ grantPermissions(regularUser1.getShortName(), Collections.singleton("SYSTEM.SEQUENCE"), Action.WRITE,
Action.READ, Action.EXEC);
- grantPermissions(unprivilegedUser.getShortUserName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
+ grantPermissions(unprivilegedUser.getShortName(), Collections.singleton("SYSTEM:SEQUENCE"), Action.WRITE,
Action.READ, Action.EXEC);
}
} catch (Throwable e) {
@@ -165,40 +74,19 @@ public class TableDDLPermissionsIT{
}
}
- @Parameters(name = "isNamespaceMapped={0}") // name is used by failsafe as file name in reports
- public static Collection<Boolean> data() {
- return Arrays.asList(true, false);
- }
-
- @BeforeClass
- public static void doSetup() throws Exception {
- SUPERUSER = System.getProperty("user.name");
- //setUpTestDriver(new ReadOnlyProps(serverProps.entrySet().iterator()), new ReadOnlyProps(clientProps.entrySet().iterator()));
- }
-
- protected static String getUrl() {
- return "jdbc:phoenix:localhost:" + testUtil.getZkCluster().getClientPort() + ":/hbase";
- }
-
- public Connection getConnection() throws SQLException{
- Properties props = new Properties();
- props.setProperty(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(isNamespaceMapped));
- return DriverManager.getConnection(getUrl(),props);
- }
-
@Test
public void testSchemaPermissions() throws Throwable{
if (!isNamespaceMapped) { return; }
try {
- startNewMiniCluster(null);
+ startNewMiniCluster();
grantSystemTableAccess();
final String schemaName = "TEST_SCHEMA_PERMISSION";
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
+ superUser1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
try {
- AccessControlClient.grant(getUtility().getConnection(), regularUser.getShortUserName(),
+ AccessControlClient.grant(getUtility().getConnection(), regularUser1.getShortName(),
Action.ADMIN);
} catch (Throwable e) {
if (e instanceof Exception) {
@@ -210,26 +98,20 @@ public class TableDDLPermissionsIT{
return null;
}
});
- verifyAllowed(createSchema(schemaName), regularUser);
+ verifyAllowed(createSchema(schemaName), regularUser1);
// Unprivileged user cannot drop a schema
- verifyDenied(dropSchema(schemaName), unprivilegedUser);
- verifyDenied(createSchema(schemaName), unprivilegedUser);
+ verifyDenied(dropSchema(schemaName), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(createSchema(schemaName), AccessDeniedException.class, unprivilegedUser);
- verifyAllowed(dropSchema(schemaName), regularUser);
+ verifyAllowed(dropSchema(schemaName), regularUser1);
} finally {
revokeAll();
}
}
@Test
- public void testAutomaticGrantDisabled() throws Throwable{
- testIndexAndView(false);
- }
-
- public void testIndexAndView(boolean isAutomaticGrant) throws Throwable {
- Configuration conf = new Configuration();
- conf.set(QueryServices.PHOENIX_AUTOMATIC_GRANT_ENABLED, Boolean.toString(isAutomaticGrant));
- startNewMiniCluster(conf);
+ public void testAutomaticGrantWithIndexAndView() throws Throwable {
+ startNewMiniCluster();
final String schema = "TEST_INDEX_VIEW";
final String tableName = "TABLE_DDL_PERMISSION_IT";
final String phoenixTableName = schema + "." + tableName;
@@ -244,17 +126,17 @@ public class TableDDLPermissionsIT{
final String viewIndexName2 = tableName + "_VIDX2";
grantSystemTableAccess();
try {
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
+ superUser1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
try {
- verifyAllowed(createSchema(schema), superUser);
+ verifyAllowed(createSchema(schema), superUser1);
if (isNamespaceMapped) {
- grantPermissions(regularUser.getShortUserName(), schema, Action.CREATE);
+ grantPermissions(regularUser1.getShortName(), schema, Action.CREATE);
grantPermissions(AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS), schema, Action.CREATE);
} else {
- grantPermissions(regularUser.getShortUserName(),
+ grantPermissions(regularUser1.getShortName(),
NamespaceDescriptor.DEFAULT_NAMESPACE.getName(), Action.CREATE);
grantPermissions(AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS),
NamespaceDescriptor.DEFAULT_NAMESPACE.getName(), Action.CREATE);
@@ -271,29 +153,29 @@ public class TableDDLPermissionsIT{
}
});
- verifyAllowed(createTable(phoenixTableName), regularUser);
- verifyAllowed(createIndex(indexName1, phoenixTableName), regularUser);
- verifyAllowed(createView(viewName1, phoenixTableName), regularUser);
- verifyAllowed(createLocalIndex(lIndexName1, phoenixTableName), regularUser);
- verifyAllowed(createIndex(viewIndexName1, viewName1), regularUser);
- verifyAllowed(createIndex(viewIndexName2, viewName1), regularUser);
- verifyAllowed(createView(viewName4, viewName1), regularUser);
- verifyAllowed(readTable(phoenixTableName), regularUser);
-
- verifyDenied(createIndex(indexName2, phoenixTableName), unprivilegedUser);
- verifyDenied(createView(viewName2, phoenixTableName), unprivilegedUser);
- verifyDenied(createView(viewName3, viewName1), unprivilegedUser);
- verifyDenied(dropView(viewName1), unprivilegedUser);
+ verifyAllowed(createTable(phoenixTableName), regularUser1);
+ verifyAllowed(createIndex(indexName1, phoenixTableName), regularUser1);
+ verifyAllowed(createView(viewName1, phoenixTableName), regularUser1);
+ verifyAllowed(createLocalIndex(lIndexName1, phoenixTableName), regularUser1);
+ verifyAllowed(createIndex(viewIndexName1, viewName1), regularUser1);
+ verifyAllowed(createIndex(viewIndexName2, viewName1), regularUser1);
+ verifyAllowed(createView(viewName4, viewName1), regularUser1);
+ verifyAllowed(readTable(phoenixTableName), regularUser1);
+
+ verifyDenied(createIndex(indexName2, phoenixTableName), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(createView(viewName2, phoenixTableName),AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(createView(viewName3, viewName1), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(dropView(viewName1), AccessDeniedException.class, unprivilegedUser);
- verifyDenied(dropIndex(indexName1, phoenixTableName), unprivilegedUser);
- verifyDenied(dropTable(phoenixTableName), unprivilegedUser);
- verifyDenied(rebuildIndex(indexName1, phoenixTableName), unprivilegedUser);
- verifyDenied(addColumn(phoenixTableName, "val1"), unprivilegedUser);
- verifyDenied(dropColumn(phoenixTableName, "val"), unprivilegedUser);
- verifyDenied(addProperties(phoenixTableName, "GUIDE_POSTS_WIDTH", "100"), unprivilegedUser);
+ verifyDenied(dropIndex(indexName1, phoenixTableName), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(dropTable(phoenixTableName), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(rebuildIndex(indexName1, phoenixTableName), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(addColumn(phoenixTableName, "val1"), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(dropColumn(phoenixTableName, "val"), AccessDeniedException.class, unprivilegedUser);
+ verifyDenied(addProperties(phoenixTableName, "GUIDE_POSTS_WIDTH", "100"), AccessDeniedException.class, unprivilegedUser);
// Granting read permission to unprivileged user, now he should be able to create view but not index
- grantPermissions(unprivilegedUser.getShortUserName(),
+ grantPermissions(unprivilegedUser.getShortName(),
Collections.singleton(
SchemaUtil.getPhysicalHBaseTableName(schema, tableName, isNamespaceMapped).getString()),
Action.READ, Action.EXEC);
@@ -301,52 +183,18 @@ public class TableDDLPermissionsIT{
Collections.singleton(
SchemaUtil.getPhysicalHBaseTableName(schema, tableName, isNamespaceMapped).getString()),
Action.READ, Action.EXEC);
- verifyDenied(createIndex(indexName2, phoenixTableName), unprivilegedUser);
- if (!isAutomaticGrant) {
- // Automatic grant will read access for all indexes
- verifyDenied(createView(viewName2, phoenixTableName), unprivilegedUser);
-
- // Granting read permission to unprivileged user on index so that a new view can read a index as well,
- // now
- // he should be able to create view but not index
- grantPermissions(unprivilegedUser.getShortUserName(),
- Collections.singleton(SchemaUtil
- .getPhysicalHBaseTableName(schema, indexName1, isNamespaceMapped).getString()),
- Action.READ, Action.EXEC);
- verifyDenied(createView(viewName3, viewName1), unprivilegedUser);
- }
-
+ verifyDenied(createIndex(indexName2, phoenixTableName), AccessDeniedException.class, unprivilegedUser);
verifyAllowed(createView(viewName2, phoenixTableName), unprivilegedUser);
-
- if (!isAutomaticGrant) {
- // Grant access to view index for parent view
- grantPermissions(unprivilegedUser.getShortUserName(),
- Collections.singleton(Bytes.toString(MetaDataUtil.getViewIndexPhysicalName(SchemaUtil
- .getPhysicalHBaseTableName(schema, tableName, isNamespaceMapped).getBytes()))),
- Action.READ, Action.EXEC);
- }
verifyAllowed(createView(viewName3, viewName1), unprivilegedUser);
// Grant create permission in namespace
if (isNamespaceMapped) {
- grantPermissions(unprivilegedUser.getShortUserName(), schema, Action.CREATE);
+ grantPermissions(unprivilegedUser.getShortName(), schema, Action.CREATE);
} else {
- grantPermissions(unprivilegedUser.getShortUserName(), NamespaceDescriptor.DEFAULT_NAMESPACE.getName(),
+ grantPermissions(unprivilegedUser.getShortName(), NamespaceDescriptor.DEFAULT_NAMESPACE.getName(),
Action.CREATE);
}
- if (!isAutomaticGrant) {
- verifyDenied(createIndex(indexName2, phoenixTableName), unprivilegedUser);
- // Give user of data table access to index table which will be created by unprivilegedUser
- grantPermissions(regularUser.getShortUserName(),
- Collections.singleton(SchemaUtil
- .getPhysicalHBaseTableName(schema, indexName2, isNamespaceMapped).getString()),
- Action.WRITE);
- verifyDenied(createIndex(indexName2, phoenixTableName), unprivilegedUser);
- grantPermissions(regularUser.getShortUserName(),
- Collections.singleton(SchemaUtil
- .getPhysicalHBaseTableName(schema, indexName2, isNamespaceMapped).getString()),
- Action.WRITE, Action.READ, Action.CREATE, Action.EXEC, Action.ADMIN);
- }
+
// we should be able to read the data from another index as well to which we have not given any access to
// this user
verifyAllowed(createIndex(indexName2, phoenixTableName), unprivilegedUser);
@@ -355,19 +203,19 @@ public class TableDDLPermissionsIT{
verifyAllowed(rebuildIndex(indexName2, phoenixTableName), unprivilegedUser);
// data table user should be able to read new index
- verifyAllowed(rebuildIndex(indexName2, phoenixTableName), regularUser);
- verifyAllowed(readTable(phoenixTableName, indexName2), regularUser);
-
- verifyAllowed(readTable(phoenixTableName), regularUser);
- verifyAllowed(rebuildIndex(indexName1, phoenixTableName), regularUser);
- verifyAllowed(addColumn(phoenixTableName, "val1"), regularUser);
- verifyAllowed(addProperties(phoenixTableName, "GUIDE_POSTS_WIDTH", "100"), regularUser);
- verifyAllowed(dropView(viewName1), regularUser);
- verifyAllowed(dropView(viewName2), regularUser);
- verifyAllowed(dropColumn(phoenixTableName, "val1"), regularUser);
- verifyAllowed(dropIndex(indexName2, phoenixTableName), regularUser);
- verifyAllowed(dropIndex(indexName1, phoenixTableName), regularUser);
- verifyAllowed(dropTable(phoenixTableName), regularUser);
+ verifyAllowed(rebuildIndex(indexName2, phoenixTableName), regularUser1);
+ verifyAllowed(readTable(phoenixTableName, indexName2), regularUser1);
+
+ verifyAllowed(readTable(phoenixTableName), regularUser1);
+ verifyAllowed(rebuildIndex(indexName1, phoenixTableName), regularUser1);
+ verifyAllowed(addColumn(phoenixTableName, "val1"), regularUser1);
+ verifyAllowed(addProperties(phoenixTableName, "GUIDE_POSTS_WIDTH", "100"), regularUser1);
+ verifyAllowed(dropView(viewName1), regularUser1);
+ verifyAllowed(dropView(viewName2), regularUser1);
+ verifyAllowed(dropColumn(phoenixTableName, "val1"), regularUser1);
+ verifyAllowed(dropIndex(indexName2, phoenixTableName), regularUser1);
+ verifyAllowed(dropIndex(indexName1, phoenixTableName), regularUser1);
+ verifyAllowed(dropTable(phoenixTableName), regularUser1);
// check again with super users
verifyAllowed(createTable(phoenixTableName), superUser2);
@@ -381,312 +229,5 @@ public class TableDDLPermissionsIT{
revokeAll();
}
}
-
-
- @Test
- public void testAutomaticGrantEnabled() throws Throwable{
- testIndexAndView(true);
- }
-
- private void revokeAll() throws IOException, Throwable {
- AccessControlClient.revoke(getUtility().getConnection(), AuthUtil.toGroupEntry(GROUP_SYSTEM_ACCESS),Action.values() );
- AccessControlClient.revoke(getUtility().getConnection(), regularUser.getShortUserName(),Action.values() );
- AccessControlClient.revoke(getUtility().getConnection(), unprivilegedUser.getShortUserName(),Action.values() );
-
- }
-
- protected void grantPermissions(String groupEntry, Action... actions) throws IOException, Throwable {
- AccessControlClient.grant(getUtility().getConnection(), groupEntry, actions);
- }
-
- private 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;
- }
- };
-
- }
-
- private 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;
- }
- };
- }
-
- private AccessTestAction readTable(final String tableName) throws SQLException {
- return readTable(tableName,null);
- }
- private 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()) {
- ResultSet rs = stmt.executeQuery("SELECT "+(indexName!=null?"/*+ INDEX("+tableName+" "+indexName+")*/":"")+" pk, data,val FROM " + tableName +" where data>='0'");
- 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;
- }
- };
- }
-
- public static HBaseTestingUtility getUtility(){
- return testUtil;
- }
-
- private void grantPermissions(String toUser, Set<String> tablesToGrant, Action... actions) throws Throwable {
- for (String table : tablesToGrant) {
- AccessControlClient.grant(getUtility().getConnection(), TableName.valueOf(table), toUser, null, null,
- actions);
- }
- }
-
- private void grantPermissions(String toUser, String namespace, Action... actions) throws Throwable {
- AccessControlClient.grant(getUtility().getConnection(), namespace, toUser, actions);
- }
-
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private AccessTestAction createView(final String viewName, 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 VIEW " + viewName + " AS SELECT * FROM " + dataTable));
- }
- return null;
- }
- };
- }
-
- private AccessTestAction createIndex(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 INDEX " + indexName + " on " + dataTable + "(data)"));
- }
- return null;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- private 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;
- }
- };
- }
-
- static interface AccessTestAction extends PrivilegedExceptionAction<Object> { }
-
- @After
- public void cleanup() throws Exception {
- if (null != testUtil) {
- testUtil.shutdownMiniCluster();
- testUtil = null;
- }
- }
-
- /** This fails only in case of ADE or empty list for any of the users. */
- private void verifyAllowed(AccessTestAction action, UserGroupInformation... users) throws Exception {
- for (UserGroupInformation user : users) {
- verifyAllowed(user, action);
- }
- }
-
- /** This passes only in case of ADE for all users. */
- private void verifyDenied(AccessTestAction action, UserGroupInformation... users) throws Exception {
- for (UserGroupInformation user : users) {
- verifyDenied(user, action);
- }
- }
-
- /** This fails only in case of ADE or empty list for any of the actions. */
- private void verifyAllowed(UserGroupInformation user, AccessTestAction... actions) throws Exception {
- for (AccessTestAction action : actions) {
- try {
- Object obj = user.doAs(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.getShortUserName() + "'");
- }
- }
- } catch (AccessDeniedException ade) {
- fail("Expected action to pass for user '" + user.getShortUserName() + "' but was denied");
- }
- }
- }
-
- /** This passes only in case of ADE for all actions. */
- private void verifyDenied(UserGroupInformation user, AccessTestAction... actions) throws Exception {
- for (AccessTestAction action : actions) {
- try {
- user.doAs(action);
- fail("Expected exception was not thrown for user '" + user.getShortUserName() + "'");
- } catch (IOException e) {
- fail("Expected exception was not thrown for user '" + user.getShortUserName() + "'");
- } catch (UndeclaredThrowableException ute) {
- Throwable ex = ute.getUndeclaredThrowable();
-
- if (ex instanceof PhoenixIOException) {
- if (ex.getCause() instanceof AccessDeniedException) {
- // expected result
- validateAccessDeniedException((AccessDeniedException) ex.getCause());
- 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.getShortUserName() + "'");
- }
- }
- private void validateAccessDeniedException(AccessDeniedException ade) {
- String msg = ade.getMessage();
- assertTrue("Exception contained unexpected message: '" + msg + "'",
- !msg.contains("is not the scanner owner"));
- }
}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/antlr3/PhoenixSQL.g
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index 93e0ede..8c9c135 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -118,6 +118,7 @@ tokens
UNION='union';
FUNCTION='function';
AS='as';
+ TO='to';
TEMPORARY='temporary';
RETURNS='returns';
USING='using';
@@ -144,6 +145,8 @@ tokens
DUPLICATE = 'duplicate';
IGNORE = 'ignore';
IMMUTABLE = 'immutable';
+ GRANT = 'grant';
+ REVOKE = 'revoke';
}
@@ -430,6 +433,8 @@ oneStatement returns [BindableStatement ret]
| s=delete_jar_node
| s=alter_session_node
| s=create_sequence_node
+ | s=grant_permission_node
+ | s=revoke_permission_node
| s=drop_sequence_node
| s=drop_schema_node
| s=use_schema_node
@@ -458,6 +463,30 @@ create_schema_node returns [CreateSchemaStatement ret]
{ret = factory.createSchema(s, ex!=null); }
;
+// Parse a grant permission statement
+grant_permission_node returns [ChangePermsStatement ret]
+ : GRANT p=literal (ON ((TABLE)? table=table_name | s=SCHEMA schema=identifier))? TO (g=GROUP)? ug=literal
+ {
+ String permsString = SchemaUtil.normalizeLiteral(p);
+ if (permsString != null && permsString.length() > 5) {
+ throw new RuntimeException("Permissions String length should be less than 5 characters");
+ }
+ $ret = factory.changePermsStatement(permsString, s!=null, table, schema, g!=null, ug, Boolean.TRUE);
+ }
+ ;
+
+// Parse a revoke permission statement
+revoke_permission_node returns [ChangePermsStatement ret]
+ : REVOKE (p=literal)? (ON ((TABLE)? table=table_name | s=SCHEMA schema=identifier))? FROM (g=GROUP)? ug=literal
+ {
+ String permsString = SchemaUtil.normalizeLiteral(p);
+ if (permsString != null && permsString.length() > 5) {
+ throw new RuntimeException("Permissions String length should be less than 5 characters");
+ }
+ $ret = factory.changePermsStatement(permsString, s!=null, table, schema, g!=null, ug, Boolean.FALSE);
+ }
+ ;
+
// Parse a create view statement.
create_view_node returns [CreateTableStatement ret]
: CREATE VIEW (IF NOT ex=EXISTS)? t=from_table_name
@@ -1161,7 +1190,6 @@ BIND_NAME
: COLON (DIGIT)+
;
-
NAME
: LETTER (FIELDCHAR)*
| '\"' (DBL_QUOTE_CHAR)* '\"'
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/PhoenixAccessController.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/PhoenixAccessController.java b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/PhoenixAccessController.java
index 8437b37..a4bc857 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/PhoenixAccessController.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/PhoenixAccessController.java
@@ -75,8 +75,6 @@ public class PhoenixAccessController extends BaseMetaDataEndpointObserver {
private ArrayList<BaseMasterAndRegionObserver> accessControllers;
private boolean accessCheckEnabled;
private UserProvider userProvider;
- private boolean isAutomaticGrantEnabled;
- private boolean isStrictMode;
public static final Log LOG = LogFactory.getLog(PhoenixAccessController.class);
private static final Log AUDITLOG =
LogFactory.getLog("SecurityLogger."+PhoenixAccessController.class.getName());
@@ -114,8 +112,6 @@ public class PhoenixAccessController extends BaseMetaDataEndpointObserver {
Configuration conf = env.getConfiguration();
this.accessCheckEnabled = conf.getBoolean(QueryServices.PHOENIX_ACLS_ENABLED,
QueryServicesOptions.DEFAULT_PHOENIX_ACLS_ENABLED);
- this.isAutomaticGrantEnabled=conf.getBoolean(QueryServices.PHOENIX_AUTOMATIC_GRANT_ENABLED,
- QueryServicesOptions.DEFAULT_PHOENIX_AUTOMATIC_GRANT_ENABLED);
if (!this.accessCheckEnabled) {
LOG.warn("PhoenixAccessController has been loaded with authorization checks disabled.");
}
@@ -127,8 +123,6 @@ public class PhoenixAccessController extends BaseMetaDataEndpointObserver {
}
// set the user-provider.
this.userProvider = UserProvider.instantiate(env.getConfiguration());
- this.isStrictMode = conf.getBoolean(QueryServices.PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED,
- QueryServicesOptions.DEFAULT_PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED);
// init superusers and add the server principal (if using security)
// or process owner as default super user.
Superusers.initialize(env.getConfiguration());
@@ -223,23 +217,12 @@ public class PhoenixAccessController extends BaseMetaDataEndpointObserver {
public void handleRequireAccessOnDependentTable(String request, String userName, TableName dependentTable,
String requestTable, Set<Action> requireAccess, Set<Action> accessExists) throws IOException {
- if (!isStrictMode) {
- AUDITLOG.warn("Strict mode is not enabled, so " + request + " is allowed but User:" + userName
- + " will not have following access " + requireAccess + " to the existing dependent physical table "
- + dependentTable);
- return;
- }
- if (isAutomaticGrantEnabled) {
- Set<Action> unionSet = new HashSet<Action>();
- unionSet.addAll(requireAccess);
- unionSet.addAll(accessExists);
- AUDITLOG.info(request + ": Automatically granting access to index table during creation of view:"
- + requestTable + authString(userName, dependentTable, requireAccess));
- grantPermissions(userName, dependentTable.getName(), unionSet.toArray(new Action[0]));
- } else {
- throw new AccessDeniedException(
- "Insufficient permissions for users of dependent table" + authString(userName, dependentTable, requireAccess));
- }
+ Set<Action> unionSet = new HashSet<Action>();
+ unionSet.addAll(requireAccess);
+ unionSet.addAll(accessExists);
+ AUDITLOG.info(request + ": Automatically granting access to index table during creation of view:"
+ + requestTable + authString(userName, dependentTable, requireAccess));
+ grantPermissions(userName, dependentTable.getName(), unionSet.toArray(new Action[0]));
}
private void grantPermissions(final String toUser, final byte[] table, final Action... actions) throws IOException {
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index e51fd9f..2301c32 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -229,6 +229,7 @@ public enum SQLExceptionCode {
return new TableAlreadyExistsException(info.getSchemaName(), info.getTableName());
}
}),
+ TABLES_NOT_IN_SYNC(1140, "42M05", "Tables not in sync for some properties."),
// Syntax error
TYPE_NOT_SUPPORTED_FOR_OPERATOR(1014, "42Y01", "The operator does not support the operand type."),
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index 174e643..384c8cc 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -97,6 +97,7 @@ import org.apache.phoenix.parse.AliasedNode;
import org.apache.phoenix.parse.AlterIndexStatement;
import org.apache.phoenix.parse.AlterSessionStatement;
import org.apache.phoenix.parse.BindableStatement;
+import org.apache.phoenix.parse.ChangePermsStatement;
import org.apache.phoenix.parse.CloseStatement;
import org.apache.phoenix.parse.ColumnDef;
import org.apache.phoenix.parse.ColumnName;
@@ -212,8 +213,9 @@ public class PhoenixStatement implements Statement, SQLCloseable {
QUERY("queried", false),
DELETE("deleted", true),
UPSERT("upserted", true),
- UPGRADE("upgrade", true);
-
+ UPGRADE("upgrade", true),
+ ADMIN("admin", true);
+
private final String toString;
private final boolean isMutation;
Operation(String toString, boolean isMutation) {
@@ -1153,6 +1155,33 @@ public class PhoenixStatement implements Statement, SQLCloseable {
}
}
+ private static class ExecutableChangePermsStatement extends ChangePermsStatement implements CompilableStatement {
+
+ public ExecutableChangePermsStatement (String permsString, boolean isSchemaName, TableName tableName,
+ String schemaName, boolean isGroupName, LiteralParseNode userOrGroup, boolean isGrantStatement) {
+ super(permsString, isSchemaName, tableName, schemaName, isGroupName, userOrGroup, isGrantStatement);
+ }
+
+ @Override
+ public MutationPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+ final StatementContext context = new StatementContext(stmt);
+
+ return new BaseMutationPlan(context, this.getOperation()) {
+
+ @Override
+ public ExplainPlan getExplainPlan() throws SQLException {
+ return new ExplainPlan(Collections.singletonList("GRANT PERMISSION"));
+ }
+
+ @Override
+ public MutationState execute() throws SQLException {
+ MetaDataClient client = new MetaDataClient(getContext().getConnection());
+ return client.changePermissions(ExecutableChangePermsStatement.this);
+ }
+ };
+ }
+ }
+
private static class ExecutableDropIndexStatement extends DropIndexStatement implements CompilableStatement {
public ExecutableDropIndexStatement(NamedNode indexName, TableName tableName, boolean ifExists) {
@@ -1558,6 +1587,13 @@ public class PhoenixStatement implements Statement, SQLCloseable {
public ExecuteUpgradeStatement executeUpgrade() {
return new ExecutableExecuteUpgradeStatement();
}
+
+ @Override
+ public ExecutableChangePermsStatement changePermsStatement(String permsString, boolean isSchemaName, TableName tableName,
+ String schemaName, boolean isGroupName, LiteralParseNode userOrGroup, boolean isGrantStatement) {
+ return new ExecutableChangePermsStatement(permsString, isSchemaName, tableName, schemaName, isGroupName, userOrGroup,isGrantStatement);
+ }
+
}
static class PhoenixStatementParser extends SQLParser {
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/parse/ChangePermsStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ChangePermsStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ChangePermsStatement.java
new file mode 100644
index 0000000..0eae26f
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ChangePermsStatement.java
@@ -0,0 +1,102 @@
+/*
+ * 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.parse;
+
+import org.antlr.runtime.RecognitionException;
+import org.apache.hadoop.hbase.AuthUtil;
+import org.apache.hadoop.hbase.security.access.Permission;
+import org.apache.phoenix.exception.PhoenixParserException;
+import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.util.SchemaUtil;
+
+import java.util.Arrays;
+
+/**
+ * See PHOENIX-672, Use GRANT/REVOKE statements to assign or remove permissions for a user OR group on a table OR namespace
+ * Permissions are managed by HBase using hbase:acl table, Allowed permissions are RWXCA
+ */
+public class ChangePermsStatement implements BindableStatement {
+
+ private Permission.Action[] permsList;
+ private TableName tableName;
+ private String schemaName;
+ private String name;
+ // Grant/Revoke statements are differentiated based on this boolean
+ private boolean isGrantStatement;
+
+ public ChangePermsStatement(String permsString, boolean isSchemaName,
+ TableName tableName, String schemaName, boolean isGroupName, LiteralParseNode ugNode, boolean isGrantStatement) {
+ // PHOENIX-672 HBase API doesn't allow to revoke specific permissions, hence this parameter will be ignored here.
+ // To comply with SQL standards, we may support the user given permissions to revoke specific permissions in future.
+ // GRANT permissions statement requires this parameter and the parsing will fail if it is not specified in SQL
+ if(permsString != null) {
+ Permission permission = new Permission(permsString.getBytes());
+ permsList = permission.getActions();
+ }
+ if(isSchemaName) {
+ this.schemaName = SchemaUtil.normalizeIdentifier(schemaName);
+ } else {
+ this.tableName = tableName;
+ }
+ name = SchemaUtil.normalizeLiteral(ugNode);
+ name = isGroupName ? AuthUtil.toGroupEntry(name) : name;
+ this.isGrantStatement = isGrantStatement;
+ }
+
+ public Permission.Action[] getPermsList() {
+ return permsList;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public TableName getTableName() {
+ return tableName;
+ }
+
+ public String getSchemaName() {
+ return schemaName;
+ }
+
+ public boolean isGrantStatement() {
+ return isGrantStatement;
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer = this.isGrantStatement() ? buffer.append("GRANT ") : buffer.append("REVOKE ");
+ buffer.append("permissions requested for user/group: " + this.getName());
+ if (this.getSchemaName() != null) {
+ buffer.append(" for Schema: " + this.getSchemaName());
+ } else if (this.getTableName() != null) {
+ buffer.append(" for Table: " + this.getTableName());
+ }
+ buffer.append(" Permissions: " + Arrays.toString(this.getPermsList()));
+ return buffer.toString();
+ }
+
+ @Override
+ public int getBindCount() {
+ return 0;
+ }
+
+ @Override
+ public PhoenixStatement.Operation getOperation() {
+ return PhoenixStatement.Operation.ADMIN;
+ }
+}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index 0058f38..32c3d8d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -25,7 +25,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.collect.ArrayListMultimap;
@@ -925,4 +924,10 @@ public class ParseNodeFactory {
public UseSchemaStatement useSchema(String schemaName) {
return new UseSchemaStatement(schemaName);
}
+
+ public ChangePermsStatement changePermsStatement(String permsString, boolean isSchemaName, TableName tableName
+ , String schemaName, boolean isGroupName, LiteralParseNode userOrGroup, boolean isGrantStatement) {
+ return new ChangePermsStatement(permsString, isSchemaName, tableName, schemaName, isGroupName, userOrGroup, isGrantStatement);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index 7a255a1..08aadfb 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -103,6 +103,7 @@ import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.NamespaceNotFoundException;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Append;
@@ -190,6 +191,7 @@ import org.apache.phoenix.schema.EmptySequenceCacheException;
import org.apache.phoenix.schema.FunctionNotFoundException;
import org.apache.phoenix.schema.MetaDataClient;
import org.apache.phoenix.schema.MetaDataSplitPolicy;
+import org.apache.phoenix.schema.NewerSchemaAlreadyExistsException;
import org.apache.phoenix.schema.NewerTableAlreadyExistsException;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PColumnFamily;
@@ -1224,7 +1226,7 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
SQLExceptionCode.INCONSISTENT_NAMESPACE_MAPPING_PROPERTIES)
.setMessage(
"Ensure that config " + QueryServices.IS_NAMESPACE_MAPPING_ENABLED
- + " is consitent on client and server.")
+ + " is consistent on client and server.")
.build().buildException(); }
lowestClusterHBaseVersion = minHBaseVersion;
} catch (SQLException e) {
@@ -2460,6 +2462,11 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
logger.warn("Could not check for Phoenix SYSTEM tables, assuming they exist and are properly configured");
checkClientServerCompatibility(SchemaUtil.getPhysicalName(SYSTEM_CATALOG_NAME_BYTES, getProps()).getName());
success = true;
+ } else if (!Iterables.isEmpty(Iterables.filter(Throwables.getCausalChain(e), NamespaceNotFoundException.class))) {
+ // This exception is only possible if SYSTEM namespace mapping is enabled and SYSTEM namespace is missing
+ // It implies that SYSTEM tables are not created and hence we shouldn't provide a connection
+ AccessDeniedException ade = new AccessDeniedException("Insufficient permissions to create SYSTEM namespace and SYSTEM Tables");
+ initializationException = ServerUtil.parseServerException(ade);
} else {
initializationException = e;
}
@@ -2471,8 +2478,19 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
// with SYSTEM Namespace. (See PHOENIX-4227 https://issues.apache.org/jira/browse/PHOENIX-4227)
if (SchemaUtil.isNamespaceMappingEnabled(PTableType.SYSTEM,
ConnectionQueryServicesImpl.this.getProps())) {
- metaConnection.createStatement().execute("CREATE SCHEMA IF NOT EXISTS "
- + PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA);
+ try {
+ metaConnection.createStatement().execute("CREATE SCHEMA IF NOT EXISTS "
+ + PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA);
+ } catch (NewerSchemaAlreadyExistsException e) {
+ // Older clients with appropriate perms may try getting a new connection
+ // This results in NewerSchemaAlreadyExistsException, so we can safely ignore it here
+ } catch (PhoenixIOException e) {
+ if (!Iterables.isEmpty(Iterables.filter(Throwables.getCausalChain(e), AccessDeniedException.class))) {
+ // Ignore ADE
+ } else {
+ throw e;
+ }
+ }
}
if (!ConnectionQueryServicesImpl.this.upgradeRequired.get()) {
createOtherSystemTables(metaConnection, hBaseAdmin);
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
index 7607388..851ba9a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
@@ -149,6 +149,7 @@ public interface QueryConstants {
public enum JoinType {INNER, LEFT_OUTER}
public final static String SYSTEM_SCHEMA_NAME = "SYSTEM";
public final static byte[] SYSTEM_SCHEMA_NAME_BYTES = Bytes.toBytes(SYSTEM_SCHEMA_NAME);
+ public final static String HBASE_DEFAULT_SCHEMA_NAME = "default";
public final static String PHOENIX_METADATA = "table";
public final static String OFFSET_ROW_KEY = "_OFFSET_";
public final static byte[] OFFSET_ROW_KEY_BYTES = Bytes.toBytes(OFFSET_ROW_KEY);
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
index b9ed734..59f7385 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
@@ -262,8 +262,6 @@ public interface QueryServices extends SQLCloseable {
public static final String UPLOAD_BINARY_DATA_TYPE_ENCODING = "phoenix.upload.binaryDataType.encoding";
// Toggle for server-written updates to SYSTEM.CATALOG
public static final String PHOENIX_ACLS_ENABLED = "phoenix.acls.enabled";
- public static final String PHOENIX_AUTOMATIC_GRANT_ENABLED = "phoenix.security.automatic.grant.enabled";
- public static final String PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED = "phoenix.security.strict.mode.enabled";
public static final String INDEX_ASYNC_BUILD_ENABLED = "phoenix.index.async.build.enabled";
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
index a586c28..3ceb084 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
@@ -59,13 +59,11 @@ import static org.apache.phoenix.query.QueryServices.MIN_STATS_UPDATE_FREQ_MS_AT
import static org.apache.phoenix.query.QueryServices.MUTATE_BATCH_SIZE_ATTRIB;
import static org.apache.phoenix.query.QueryServices.NUM_RETRIES_FOR_SCHEMA_UPDATE_CHECK;
import static org.apache.phoenix.query.QueryServices.PHOENIX_ACLS_ENABLED;
-import static org.apache.phoenix.query.QueryServices.PHOENIX_AUTOMATIC_GRANT_ENABLED;
import static org.apache.phoenix.query.QueryServices.PHOENIX_QUERY_SERVER_CLUSTER_BASE_PATH;
import static org.apache.phoenix.query.QueryServices.PHOENIX_QUERY_SERVER_LOADBALANCER_ENABLED;
import static org.apache.phoenix.query.QueryServices.PHOENIX_QUERY_SERVER_SERVICE_NAME;
import static org.apache.phoenix.query.QueryServices.PHOENIX_QUERY_SERVER_ZK_ACL_PASSWORD;
import static org.apache.phoenix.query.QueryServices.PHOENIX_QUERY_SERVER_ZK_ACL_USERNAME;
-import static org.apache.phoenix.query.QueryServices.PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED;
import static org.apache.phoenix.query.QueryServices.QUEUE_SIZE_ATTRIB;
import static org.apache.phoenix.query.QueryServices.REGIONSERVER_INFO_PORT_ATTRIB;
import static org.apache.phoenix.query.QueryServices.RENEW_LEASE_ENABLED;
@@ -322,8 +320,6 @@ public class QueryServicesOptions {
//Security defaults
public static final boolean DEFAULT_PHOENIX_ACLS_ENABLED = false;
- public static final boolean DEFAULT_PHOENIX_AUTOMATIC_GRANT_ENABLED = false;
- public static final boolean DEFAULT_PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED = true;
//default update cache frequency
public static final int DEFAULT_UPDATE_CACHE_FREQUENCY = 0;
@@ -423,9 +419,7 @@ public class QueryServicesOptions {
.setIfUnset(STATS_COLLECTION_ENABLED, DEFAULT_STATS_COLLECTION_ENABLED)
.setIfUnset(USE_STATS_FOR_PARALLELIZATION, DEFAULT_USE_STATS_FOR_PARALLELIZATION)
.setIfUnset(UPLOAD_BINARY_DATA_TYPE_ENCODING, DEFAULT_UPLOAD_BINARY_DATA_TYPE_ENCODING)
- .setIfUnset(PHOENIX_ACLS_ENABLED, DEFAULT_PHOENIX_ACLS_ENABLED)
- .setIfUnset(PHOENIX_AUTOMATIC_GRANT_ENABLED, DEFAULT_PHOENIX_AUTOMATIC_GRANT_ENABLED)
- .setIfUnset(PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED, DEFAULT_PHOENIX_SECURITY_PERMISSION_STRICT_MODE_ENABLED);
+ .setIfUnset(PHOENIX_ACLS_ENABLED, DEFAULT_PHOENIX_ACLS_ENABLED);
// HBase sets this to 1, so we reset it to something more appropriate.
// Hopefully HBase will change this, because we can't know if a user set
// it to 1, so we'll change it.
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 338b325..1f76e90 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -113,6 +113,7 @@ import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
@@ -130,11 +131,16 @@ import java.util.Set;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.client.ClusterConnection;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.security.AccessDeniedException;
+import org.apache.hadoop.hbase.security.access.AccessControlClient;
+import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.compile.ColumnResolver;
@@ -165,6 +171,7 @@ import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.parse.AddColumnStatement;
import org.apache.phoenix.parse.AlterIndexStatement;
+import org.apache.phoenix.parse.ChangePermsStatement;
import org.apache.phoenix.parse.CloseStatement;
import org.apache.phoenix.parse.ColumnDef;
import org.apache.phoenix.parse.ColumnDefInPkConstraint;
@@ -229,6 +236,7 @@ import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.apache.phoenix.util.ScanUtil;
import org.apache.phoenix.util.SchemaUtil;
+import org.apache.phoenix.util.ServerUtil;
import org.apache.phoenix.util.StringUtil;
import org.apache.phoenix.util.TransactionUtil;
import org.apache.phoenix.util.UpgradeUtil;
@@ -4168,4 +4176,134 @@ public class MetaDataClient {
}
return new MutationState(0, 0, connection);
}
+
+ /**
+ * GRANT/REVOKE statements use this method to update HBase acl's
+ * Perms can be changed at Schema, Table or User level
+ * @throws SQLException
+ */
+ public MutationState changePermissions(ChangePermsStatement changePermsStatement) throws SQLException {
+
+ logger.info(changePermsStatement.toString());
+
+ try(HBaseAdmin admin = connection.getQueryServices().getAdmin()) {
+ ClusterConnection clusterConnection = (ClusterConnection) admin.getConnection();
+
+ if (changePermsStatement.getSchemaName() != null) {
+ // SYSTEM.CATALOG doesn't have any entry for "default" HBase namespace, hence we will bypass the check
+ if(!changePermsStatement.getSchemaName().equals(QueryConstants.HBASE_DEFAULT_SCHEMA_NAME)) {
+ FromCompiler.getResolverForSchema(changePermsStatement.getSchemaName(), connection);
+ }
+
+ changePermsOnSchema(clusterConnection, changePermsStatement);
+ } else if (changePermsStatement.getTableName() != null) {
+ PTable inputTable = PhoenixRuntime.getTable(connection,
+ SchemaUtil.normalizeFullTableName(changePermsStatement.getTableName().toString()));
+ if (!(PTableType.TABLE.equals(inputTable.getType()) || PTableType.SYSTEM.equals(inputTable.getType()))) {
+ throw new AccessDeniedException("Cannot GRANT or REVOKE permissions on INDEX TABLES or VIEWS");
+ }
+
+ // Changing perms on base table and update the perms for global and view indexes
+ // Views and local indexes are not physical tables and hence update perms is not needed
+ changePermsOnTables(clusterConnection, admin, changePermsStatement, inputTable);
+ } else {
+
+ // User can be given perms at the global level
+ changePermsOnUser(clusterConnection, changePermsStatement);
+ }
+
+ } catch (SQLException e) {
+ // Bubble up the SQL Exception
+ throw e;
+ } catch (Throwable throwable) {
+ // To change perms, the user must have ADMIN perms on that scope, otherwise it throws ADE
+ // Wrap around ADE and other exceptions to PhoenixIOException
+ throw ServerUtil.parseServerException(throwable);
+ }
+
+ return new MutationState(0, 0, connection);
+ }
+
+ private void changePermsOnSchema(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement) throws Throwable {
+ if(changePermsStatement.isGrantStatement()) {
+ AccessControlClient.grant(clusterConnection, changePermsStatement.getSchemaName(), changePermsStatement.getName(), changePermsStatement.getPermsList());
+ } else {
+ AccessControlClient.revoke(clusterConnection, changePermsStatement.getSchemaName(), changePermsStatement.getName(), Permission.Action.values());
+ }
+ }
+
+ private void changePermsOnTables(ClusterConnection clusterConnection, HBaseAdmin admin, ChangePermsStatement changePermsStatement, PTable inputTable) throws Throwable {
+
+ org.apache.hadoop.hbase.TableName tableName = SchemaUtil.getPhysicalTableName
+ (inputTable.getPhysicalName().getBytes(), inputTable.isNamespaceMapped());
+
+ changePermsOnTable(clusterConnection, changePermsStatement, tableName);
+
+ boolean schemaInconsistency = false;
+ List<PTable> inconsistentTables = null;
+
+ for(PTable indexTable : inputTable.getIndexes()) {
+ // Local Indexes don't correspond to new physical table, they are just stored in separate CF of base table.
+ if(indexTable.getIndexType().equals(IndexType.LOCAL)) {
+ continue;
+ }
+ if (inputTable.isNamespaceMapped() != indexTable.isNamespaceMapped()) {
+ schemaInconsistency = true;
+ if(inconsistentTables == null) {
+ inconsistentTables = new ArrayList<>();
+ }
+ inconsistentTables.add(indexTable);
+ continue;
+ }
+ logger.info("Updating permissions for Index Table: " +
+ indexTable.getName() + " Base Table: " + inputTable.getName());
+ tableName = SchemaUtil.getPhysicalTableName(indexTable.getPhysicalName().getBytes(), indexTable.isNamespaceMapped());
+ changePermsOnTable(clusterConnection, changePermsStatement, tableName);
+ }
+
+ if(schemaInconsistency) {
+ for(PTable table : inconsistentTables) {
+ logger.error("Fail to propagate permissions to Index Table: " + table.getName());
+ }
+ throw new TablesNotInSyncException(inputTable.getTableName().getString(),
+ inconsistentTables.get(0).getTableName().getString(), "Namespace properties");
+ }
+
+ // There will be only a single View Index Table for all the indexes created on views
+ byte[] viewIndexTableBytes = MetaDataUtil.getViewIndexPhysicalName(inputTable.getPhysicalName().getBytes());
+ tableName = org.apache.hadoop.hbase.TableName.valueOf(viewIndexTableBytes);
+ boolean viewIndexTableExists = admin.tableExists(tableName);
+ if(viewIndexTableExists) {
+ logger.info("Updating permissions for View Index Table: " +
+ Bytes.toString(viewIndexTableBytes) + " Base Table: " + inputTable.getName());
+ changePermsOnTable(clusterConnection, changePermsStatement, tableName);
+ } else {
+ if(inputTable.isMultiTenant()) {
+ logger.error("View Index Table not found for MultiTenant Table: " + inputTable.getName());
+ logger.error("Fail to propagate permissions to view Index Table: " + tableName.getNameAsString());
+ throw new TablesNotInSyncException(inputTable.getTableName().getString(),
+ Bytes.toString(viewIndexTableBytes), " View Index table should exist for MultiTenant tables");
+ }
+ }
+ }
+
+ private void changePermsOnTable(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement, org.apache.hadoop.hbase.TableName tableName)
+ throws Throwable {
+ if(changePermsStatement.isGrantStatement()) {
+ AccessControlClient.grant(clusterConnection, tableName, changePermsStatement.getName(),
+ null, null, changePermsStatement.getPermsList());
+ } else {
+ AccessControlClient.revoke(clusterConnection, tableName, changePermsStatement.getName(),
+ null, null, Permission.Action.values());
+ }
+ }
+
+ private void changePermsOnUser(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement)
+ throws Throwable {
+ if(changePermsStatement.isGrantStatement()) {
+ AccessControlClient.grant(clusterConnection, changePermsStatement.getName(), changePermsStatement.getPermsList());
+ } else {
+ AccessControlClient.revoke(clusterConnection, changePermsStatement.getName(), Permission.Action.values());
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/schema/TablesNotInSyncException.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/TablesNotInSyncException.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/TablesNotInSyncException.java
new file mode 100644
index 0000000..e58df71
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/TablesNotInSyncException.java
@@ -0,0 +1,22 @@
+package org.apache.phoenix.schema;
+
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.exception.SQLExceptionInfo;
+
+import java.sql.SQLException;
+
+/**
+ * Exception to raise when multiple tables differ in specified properties
+ * This can happen since Apache Phoenix code doesn't work atomically for many parts
+ * For example, Base table and index tables are inconsistent in namespace mapping
+ * OR View Index table doesn't exist for multi-tenant base table
+ */
+public class TablesNotInSyncException extends SQLException {
+ private static final long serialVersionUID = 1L;
+ private static SQLExceptionCode code = SQLExceptionCode.TABLES_NOT_IN_SYNC;
+
+ public TablesNotInSyncException(String table1, String table2, String diff) {
+ super(new SQLExceptionInfo.Builder(code).setMessage("Table: " + table1 + " and Table: " + table2 + " differ in " + diff).build().toString(), code.getSQLState(), code.getErrorCode());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
index 47b4b43..5b5c3a5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
@@ -58,6 +58,7 @@ import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.parse.LiteralParseNode;
import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.query.QueryServices;
@@ -205,7 +206,25 @@ public class SchemaUtil {
}
return name.toUpperCase();
}
-
+
+ /**
+ * Normalize a Literal. If literal is surrounded by single quotes,
+ * the quotes are trimmed, else full string is returned
+ * @param literal the parsed LiteralParseNode
+ * @return the normalized literal string
+ */
+ public static String normalizeLiteral(LiteralParseNode literal) {
+ if (literal == null) {
+ return null;
+ }
+ String literalString = literal.toString();
+ if (isEnclosedInSingleQuotes(literalString)) {
+ // Trim the single quotes
+ return literalString.substring(1, literalString.length()-1);
+ }
+ return literalString;
+ }
+
/**
* Normalizes the fulltableName . Uses {@linkplain normalizeIdentifier}
* @param fullTableName
@@ -221,6 +240,10 @@ public class SchemaUtil {
return normalizedTableName + normalizeIdentifier(tableName);
}
+ public static boolean isEnclosedInSingleQuotes(String name) {
+ return name!=null && name.length() > 0 && name.charAt(0)=='\'';
+ }
+
public static boolean isCaseSensitive(String name) {
return name!=null && name.length() > 0 && name.charAt(0)=='"';
}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
index 431f60b..25f59c0 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
@@ -26,6 +26,8 @@ import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.apache.hadoop.hbase.util.Pair;
@@ -56,7 +58,7 @@ public class QueryParserTest {
}
assertEquals("Expected equality:\n" + sql + "\n" + newSQL, stmt, newStmt);
}
-
+
private void parseQueryThatShouldFail(String sql) throws Exception {
try {
parseQuery(sql);
@@ -67,6 +69,48 @@ public class QueryParserTest {
}
@Test
+ public void testParseGrantQuery() throws Exception {
+
+ String sql0 = "GRANT 'RX' ON SYSTEM.\"SEQUENCE\" TO 'user'";
+ parseQuery(sql0);
+ String sql1 = "GRANT 'RWXCA' ON TABLE some_table0 TO 'user0'";
+ parseQuery(sql1);
+ String sql2 = "GRANT 'RWX' ON some_table1 TO 'user1'";
+ parseQuery(sql2);
+ String sql3 = "GRANT 'CA' ON SCHEMA some_schema2 TO 'user2'";
+ parseQuery(sql3);
+ String sql4 = "GRANT 'RXW' ON some_table3 TO GROUP 'group3'";
+ parseQuery(sql4);
+ String sql5 = "GRANT 'RXW' ON \"some_schema5\".\"some_table5\" TO GROUP 'group5'";
+ parseQuery(sql5);
+ String sql6 = "GRANT 'RWA' TO 'user6'";
+ parseQuery(sql6);
+ String sql7 = "GRANT 'A' TO GROUP 'group7'";
+ parseQuery(sql7);
+ String sql8 = "GRANT 'ARXRRRRR' TO GROUP 'group8'";
+ parseQueryThatShouldFail(sql8);
+ }
+
+ @Test
+ public void testParseRevokeQuery() throws Exception {
+
+ String sql0 = "REVOKE ON SCHEMA SYSTEM FROM 'user0'";
+ parseQuery(sql0);
+ String sql1 = "REVOKE ON SYSTEM.\"SEQUENCE\" FROM 'user1'";
+ parseQuery(sql1);
+ String sql2 = "REVOKE ON TABLE some_table2 FROM GROUP 'group2'";
+ parseQuery(sql2);
+ String sql3 = "REVOKE ON some_table3 FROM GROUP 'group2'";
+ parseQuery(sql3);
+ String sql4 = "REVOKE FROM 'user4'";
+ parseQuery(sql4);
+ String sql5 = "REVOKE FROM GROUP 'group5'";
+ parseQuery(sql5);
+ String sql6 = "REVOKE 'RRWWXAAA' FROM GROUP 'group6'";
+ parseQueryThatShouldFail(sql6);
+ }
+
+ @Test
public void testParsePreQuery0() throws Exception {
String sql = ((
"select a from b\n" +
[2/2] phoenix git commit: PHOENIX-672 Add GRANT and REVOKE commands
using HBase AccessController
Posted by td...@apache.org.
PHOENIX-672 Add GRANT and REVOKE commands using HBase AccessController
Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/88038a2d
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/88038a2d
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/88038a2d
Branch: refs/heads/master
Commit: 88038a2dacb7aa1a90015163d4d75d04793e4e11
Parents: 355ee52
Author: Karan Mehta <ka...@gmail.com>
Authored: Wed Nov 29 12:04:06 2017 -0800
Committer: Thomas D'Silva <td...@apache.org>
Committed: Wed Nov 29 20:39:19 2017 -0800
----------------------------------------------------------------------
.../phoenix/end2end/BasePermissionsIT.java | 754 +++++++++++++++++++
.../phoenix/end2end/ChangePermissionsIT.java | 269 +++++++
.../end2end/SystemTablePermissionsIT.java | 226 +-----
.../phoenix/end2end/TableDDLPermissionsIT.java | 583 ++------------
phoenix-core/src/main/antlr3/PhoenixSQL.g | 30 +-
.../coprocessor/PhoenixAccessController.java | 29 +-
.../phoenix/exception/SQLExceptionCode.java | 1 +
.../apache/phoenix/jdbc/PhoenixStatement.java | 40 +-
.../phoenix/parse/ChangePermsStatement.java | 102 +++
.../apache/phoenix/parse/ParseNodeFactory.java | 7 +-
.../query/ConnectionQueryServicesImpl.java | 24 +-
.../apache/phoenix/query/QueryConstants.java | 1 +
.../org/apache/phoenix/query/QueryServices.java | 2 -
.../phoenix/query/QueryServicesOptions.java | 8 +-
.../apache/phoenix/schema/MetaDataClient.java | 138 ++++
.../schema/TablesNotInSyncException.java | 22 +
.../org/apache/phoenix/util/SchemaUtil.java | 25 +-
.../apache/phoenix/parse/QueryParserTest.java | 46 +-
18 files changed, 1544 insertions(+), 763 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
new file mode 100644
index 0000000..9d7ef1b
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
@@ -0,0 +1,754 @@
+/*
+ * 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() + "'");
+ }
+ }
+
+ void validateAccessDeniedException(AccessDeniedException ade) {
+ String msg = ade.getMessage();
+ assertTrue("Exception contained unexpected message: '" + msg + "'",
+ !msg.contains("is not the scanner owner"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/it/java/org/apache/phoenix/end2end/ChangePermissionsIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ChangePermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ChangePermissionsIT.java
new file mode 100644
index 0000000..c023440
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ChangePermissionsIT.java
@@ -0,0 +1,269 @@
+/*
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.security.AccessDeniedException;
+import org.apache.hadoop.hbase.security.User;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.TableNotFoundException;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Test that verifies a user can read Phoenix tables with a minimal set of permissions.
+ */
+@Category(NeedsOwnMiniClusterTest.class)
+public class ChangePermissionsIT extends BasePermissionsIT {
+
+ private static final Log LOG = LogFactory.getLog(ChangePermissionsIT.class);
+
+ private static final String SCHEMA_NAME = "CHANGEPERMSSCHEMA";
+ private static final String TABLE_NAME =
+ ChangePermissionsIT.class.getSimpleName().toUpperCase();
+ private static final String FULL_TABLE_NAME = SCHEMA_NAME + "." + TABLE_NAME;
+ private static final String IDX1_TABLE_NAME = TABLE_NAME + "_IDX1";
+ private static final String IDX2_TABLE_NAME = TABLE_NAME + "_IDX2";
+ private static final String IDX3_TABLE_NAME = TABLE_NAME + "_IDX3";
+ private static final String LOCAL_IDX1_TABLE_NAME = TABLE_NAME + "_LIDX1";
+ private static final String VIEW1_TABLE_NAME = TABLE_NAME + "_V1";
+ private static final String VIEW2_TABLE_NAME = TABLE_NAME + "_V2";
+
+ public ChangePermissionsIT(boolean isNamespaceMapped) throws Exception {
+ super(isNamespaceMapped);
+ }
+
+ private void grantSystemTableAccess(User superUser, User... users) throws Exception {
+ for(User user : users) {
+ if(isNamespaceMapped) {
+ verifyAllowed(grantPermissions("RX", user, QueryConstants.SYSTEM_SCHEMA_NAME, true), superUser);
+ } else {
+ verifyAllowed(grantPermissions("RX", user, PHOENIX_SYSTEM_TABLES_IDENTIFIERS, false), superUser);
+ }
+ verifyAllowed(grantPermissions("W", user, SYSTEM_SEQUENCE_IDENTIFIER, false), superUser);
+ }
+ }
+
+ private void revokeSystemTableAccess(User superUser, User... users) throws Exception {
+ for(User user : users) {
+ if(isNamespaceMapped) {
+ verifyAllowed(revokePermissions(user, QueryConstants.SYSTEM_SCHEMA_NAME, true), superUser);
+ } else {
+ verifyAllowed(revokePermissions(user, PHOENIX_SYSTEM_TABLES_IDENTIFIERS, false), superUser);
+ }
+ verifyAllowed(revokePermissions(user, SYSTEM_SEQUENCE_IDENTIFIER, false), superUser);
+ }
+ }
+
+ /**
+ * Verify that READ and EXECUTE permissions are required on SYSTEM tables to get a Phoenix Connection
+ * Tests grant revoke permissions per user 1. if NS enabled -> on namespace 2. If NS disabled -> on tables
+ */
+ @Test
+ public void testRXPermsReqdForPhoenixConn() throws Exception {
+
+ startNewMiniCluster();
+
+ if(isNamespaceMapped) {
+ // NS is enabled, CQSI tries creating SYSCAT, we get NamespaceNotFoundException exception for "SYSTEM" NS
+ // We create custom ADE and throw it (and ignore NamespaceNotFoundException)
+ // This is because we didn't had CREATE perms to create "SYSTEM" NS
+ verifyDenied(getConnectionAction(), AccessDeniedException.class, regularUser1);
+ } else {
+ // NS is disabled, CQSI tries creating SYSCAT, Two cases here
+ // 1. First client ever --> Gets ADE, runs client server compatibility check again and gets TableNotFoundException since SYSCAT doesn't exist
+ // 2. Any other client --> Gets ADE, runs client server compatibility check again and gets AccessDeniedException since it doesn't have EXEC perms
+ verifyDenied(getConnectionAction(), TableNotFoundException.class, regularUser1);
+ }
+
+ // Phoenix Client caches connection per user
+ // If we grant permissions, get a connection and then revoke it, we can still get the cached connection
+ // However it will fail for other read queries
+ // Thus this test grants and revokes for 2 users, so that both functionality can be tested.
+ grantSystemTableAccess(superUser1, regularUser1, regularUser2);
+ verifyAllowed(getConnectionAction(), regularUser1);
+ revokeSystemTableAccess(superUser1, regularUser2);
+ verifyDenied(getConnectionAction(), AccessDeniedException.class, regularUser2);
+ }
+
+ /**
+ * Superuser grants admin perms to user1, who will in-turn grant admin perms to user2
+ * Not affected with namespace props
+ * Tests grant revoke permissions on per user global level
+ */
+ @Test
+ public void testSuperUserCanChangePerms() throws Exception {
+
+ startNewMiniCluster();
+
+ // Grant System Table access to all users, else they can't create a Phoenix connection
+ grantSystemTableAccess(superUser1, regularUser1, regularUser2, unprivilegedUser);
+
+ verifyAllowed(grantPermissions("A", regularUser1), superUser1);
+
+ verifyAllowed(readTableWithoutVerification(PhoenixDatabaseMetaData.SYSTEM_CATALOG), regularUser1);
+ verifyAllowed(grantPermissions("A", regularUser2), regularUser1);
+
+ verifyAllowed(revokePermissions(regularUser1), superUser1);
+ verifyDenied(grantPermissions("A", regularUser3), AccessDeniedException.class, regularUser1);
+
+ // Don't grant ADMIN perms to unprivilegedUser, thus unprivilegedUser is unable to control other permissions.
+ verifyAllowed(getConnectionAction(), unprivilegedUser);
+ verifyDenied(grantPermissions("ARX", regularUser4), AccessDeniedException.class, unprivilegedUser);
+ }
+
+ /**
+ * Test to verify READ permissions on table, indexes and views
+ * Tests automatic grant revoke of permissions per user on a table
+ */
+ @Test
+ public void testReadPermsOnTableIndexAndView() throws Exception {
+
+ startNewMiniCluster();
+
+ grantSystemTableAccess(superUser1, regularUser1, regularUser2, unprivilegedUser);
+
+ // Create new schema and grant CREATE permissions to a user
+ if(isNamespaceMapped) {
+ verifyAllowed(createSchema(SCHEMA_NAME), superUser1);
+ verifyAllowed(grantPermissions("C", regularUser1, SCHEMA_NAME, true), superUser1);
+ } else {
+ verifyAllowed(grantPermissions("C", regularUser1, "\"" + QueryConstants.HBASE_DEFAULT_SCHEMA_NAME + "\"", true), superUser1);
+ }
+
+ // Create new table. Create indexes, views and view indexes on top of it. Verify the contents by querying it
+ verifyAllowed(createTable(FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(readTable(FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(createIndex(IDX1_TABLE_NAME, FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(createIndex(IDX2_TABLE_NAME, FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(createLocalIndex(LOCAL_IDX1_TABLE_NAME, FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(createView(VIEW1_TABLE_NAME, FULL_TABLE_NAME), regularUser1);
+ verifyAllowed(createIndex(IDX3_TABLE_NAME, VIEW1_TABLE_NAME), regularUser1);
+
+ // RegularUser2 doesn't have any permissions. It can get a PhoenixConnection
+ // However it cannot query table, indexes or views without READ perms
+ verifyAllowed(getConnectionAction(), regularUser2);
+ verifyDenied(readTable(FULL_TABLE_NAME), AccessDeniedException.class, regularUser2);
+ verifyDenied(readTable(FULL_TABLE_NAME, IDX1_TABLE_NAME), AccessDeniedException.class, regularUser2);
+ verifyDenied(readTable(VIEW1_TABLE_NAME), AccessDeniedException.class, regularUser2);
+ verifyDenied(readTableWithoutVerification(SCHEMA_NAME + "." + IDX1_TABLE_NAME), AccessDeniedException.class, regularUser2);
+
+ // Grant READ permissions to RegularUser2 on the table
+ // Permissions should propagate automatically to relevant physical tables such as global index and view index.
+ verifyAllowed(grantPermissions("R", regularUser2, FULL_TABLE_NAME, false), regularUser1);
+ // Granting permissions directly to index tables should fail
+ verifyDenied(grantPermissions("W", regularUser2, SCHEMA_NAME + "." + IDX1_TABLE_NAME, false), AccessDeniedException.class, regularUser1);
+ // Granting permissions directly to views should fail. We expect TableNotFoundException since VIEWS are not physical tables
+ verifyDenied(grantPermissions("W", regularUser2, SCHEMA_NAME + "." + VIEW1_TABLE_NAME, false), TableNotFoundException.class, regularUser1);
+
+ // Verify that all other access are successful now
+ verifyAllowed(readTable(FULL_TABLE_NAME), regularUser2);
+ verifyAllowed(readTable(FULL_TABLE_NAME, IDX1_TABLE_NAME), regularUser2);
+ verifyAllowed(readTable(FULL_TABLE_NAME, IDX2_TABLE_NAME), regularUser2);
+ verifyAllowed(readTable(FULL_TABLE_NAME, LOCAL_IDX1_TABLE_NAME), regularUser2);
+ verifyAllowed(readTableWithoutVerification(SCHEMA_NAME + "." + IDX1_TABLE_NAME), regularUser2);
+ verifyAllowed(readTable(VIEW1_TABLE_NAME), regularUser2);
+ verifyAllowed(readMultiTenantTableWithIndex(VIEW1_TABLE_NAME), regularUser2);
+
+ // Revoke READ permissions to RegularUser2 on the table
+ // Permissions should propagate automatically to relevant physical tables such as global index and view index.
+ verifyAllowed(revokePermissions(regularUser2, FULL_TABLE_NAME, false), regularUser1);
+ // READ query should fail now
+ verifyDenied(readTable(FULL_TABLE_NAME), AccessDeniedException.class, regularUser2);
+ verifyDenied(readTableWithoutVerification(SCHEMA_NAME + "." + IDX1_TABLE_NAME), AccessDeniedException.class, regularUser2);
+
+ }
+
+ /**
+ * Verifies permissions for users present inside a group
+ */
+ @Test
+ public void testGroupUserPerms() throws Exception {
+
+ startNewMiniCluster();
+
+ if(isNamespaceMapped) {
+ verifyAllowed(createSchema(SCHEMA_NAME), superUser1);
+ }
+ verifyAllowed(createTable(FULL_TABLE_NAME), superUser1);
+
+ // Grant SYSTEM table access to GROUP_SYSTEM_ACCESS and regularUser1
+ verifyAllowed(grantPermissions("RX", GROUP_SYSTEM_ACCESS, PHOENIX_SYSTEM_TABLES_IDENTIFIERS, false), superUser1);
+ grantSystemTableAccess(superUser1, regularUser1);
+
+ // Grant Permissions to Groups (Should be automatically applicable to all users inside it)
+ verifyAllowed(grantPermissions("AR", GROUP_SYSTEM_ACCESS, FULL_TABLE_NAME, false), superUser1);
+ verifyAllowed(readTable(FULL_TABLE_NAME), groupUser);
+
+ // GroupUser is an admin and can grant perms to other users
+ verifyDenied(readTable(FULL_TABLE_NAME), AccessDeniedException.class, regularUser1);
+ verifyAllowed(grantPermissions("R", regularUser1, FULL_TABLE_NAME, false), groupUser);
+ verifyAllowed(readTable(FULL_TABLE_NAME), regularUser1);
+
+ // Revoke the perms and try accessing data again
+ verifyAllowed(revokePermissions(GROUP_SYSTEM_ACCESS, FULL_TABLE_NAME, false), superUser1);
+ verifyDenied(readTable(FULL_TABLE_NAME), AccessDeniedException.class, groupUser);
+ }
+
+ /**
+ * Tests permissions for MultiTenant Tables and view index tables
+ */
+ @Test
+ public void testMultiTenantTables() throws Exception {
+
+ startNewMiniCluster();
+
+ grantSystemTableAccess(superUser1, regularUser1, regularUser2, regularUser3);
+
+ if(isNamespaceMapped) {
+ verifyAllowed(createSchema(SCHEMA_NAME), superUser1);
+ verifyAllowed(grantPermissions("C", regularUser1, SCHEMA_NAME, true), superUser1);
+ } else {
+ verifyAllowed(grantPermissions("C", regularUser1, "\"" + QueryConstants.HBASE_DEFAULT_SCHEMA_NAME + "\"", true), superUser1);
+ }
+
+ // Create MultiTenant Table (View Index Table should be automatically created)
+ // At this point, the index table doesn't contain any data
+ verifyAllowed(createMultiTenantTable(FULL_TABLE_NAME), regularUser1);
+
+ // RegularUser2 doesn't have access yet, RegularUser1 should have RWXCA on the table
+ verifyDenied(readMultiTenantTableWithoutIndex(FULL_TABLE_NAME), AccessDeniedException.class, regularUser2);
+
+ // Grant perms to base table (Should propagate to View Index as well)
+ verifyAllowed(grantPermissions("R", regularUser2, FULL_TABLE_NAME, false), regularUser1);
+ // Try reading full table
+ verifyAllowed(readMultiTenantTableWithoutIndex(FULL_TABLE_NAME), regularUser2);
+
+ // Create tenant specific views on the table using tenant specific Phoenix Connection
+ verifyAllowed(createView(VIEW1_TABLE_NAME, FULL_TABLE_NAME, "o1"), regularUser1);
+ verifyAllowed(createView(VIEW2_TABLE_NAME, FULL_TABLE_NAME, "o2"), regularUser1);
+
+ // Create indexes on those views using tenant specific Phoenix Connection
+ // It is not possible to create indexes on tenant specific views without tenant connection
+ verifyAllowed(createIndex(IDX1_TABLE_NAME, VIEW1_TABLE_NAME, "o1"), regularUser1);
+ verifyAllowed(createIndex(IDX2_TABLE_NAME, VIEW2_TABLE_NAME, "o2"), regularUser1);
+
+ // Read the tables as regularUser2, with and without the use of Index table
+ // If perms are propagated correctly, then both of them should work
+ // The test checks if the query plan uses the index table by searching for "_IDX_" string
+ // _IDX_ is the prefix used with base table name to derieve the name of view index table
+ verifyAllowed(readMultiTenantTableWithIndex(VIEW1_TABLE_NAME, "o1"), regularUser2);
+ verifyAllowed(readMultiTenantTableWithoutIndex(VIEW2_TABLE_NAME, "o2"), regularUser2);
+ }
+}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/88038a2d/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablePermissionsIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablePermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablePermissionsIT.java
index 49202a4..bbe7114 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablePermissionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablePermissionsIT.java
@@ -16,177 +16,60 @@
*/
package org.apache.phoenix.end2end;
-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 java.io.IOException;
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.Collections;
-import java.util.HashSet;
-import java.util.Properties;
import java.util.Set;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission.Action;
-import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.phoenix.query.QueryServices;
-import org.junit.After;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Test that verifies a user can read Phoenix tables with a minimal set of permissions.
+ * Uses HBase API directly to grant/revoke permissions
*/
@Category(NeedsOwnMiniClusterTest.class)
-public class SystemTablePermissionsIT {
- private static String SUPERUSER;
-
- private static final Set<String> PHOENIX_SYSTEM_TABLES = new HashSet<>(Arrays.asList(
- "SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS", "SYSTEM.FUNCTION",
- "SYSTEM.MUTEX"));
- private static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES = new HashSet<>(
- Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS", "SYSTEM:FUNCTION",
- "SYSTEM:MUTEX"));
+public class SystemTablePermissionsIT extends BasePermissionsIT {
private static final String TABLE_NAME =
SystemTablePermissionsIT.class.getSimpleName().toUpperCase();
- private static final int NUM_RECORDS = 5;
-
- private HBaseTestingUtility testUtil = null;
- private Properties clientProperties = null;
- @BeforeClass
- public static void setup() throws Exception {
- SUPERUSER = System.getProperty("user.name");
- }
-
- private static void setCommonConfigProperties(Configuration conf) {
- conf.set("hbase.coprocessor.master.classes",
- "org.apache.hadoop.hbase.security.access.AccessController");
- conf.set("hbase.coprocessor.region.classes",
- "org.apache.hadoop.hbase.security.access.AccessController");
- conf.set("hbase.coprocessor.regionserver.classes",
- "org.apache.hadoop.hbase.security.access.AccessController");
- conf.set("hbase.security.exec.permission.checks", "true");
- conf.set("hbase.security.authorization", "true");
- conf.set("hbase.superuser", SUPERUSER);
- }
-
- @After
- public void cleanup() throws Exception {
- if (null != testUtil) {
- testUtil.shutdownMiniCluster();
- testUtil = null;
- }
+ public SystemTablePermissionsIT(boolean isNamespaceMapped) throws Exception {
+ super(isNamespaceMapped);
}
@Test
- public void testSystemTablePermissions() throws Exception {
- testUtil = new HBaseTestingUtility();
- clientProperties = new Properties();
- Configuration conf = testUtil.getConfiguration();
- setCommonConfigProperties(conf);
- conf.set(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "false");
- clientProperties.setProperty(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "false");
- testUtil.startMiniCluster(1);
- final UserGroupInformation superUser = UserGroupInformation.createUserForTesting(
- SUPERUSER, new String[0]);
- final UserGroupInformation regularUser = UserGroupInformation.createUserForTesting(
- "user", new String[0]);
+ public void testSystemTablePermissions() throws Throwable {
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- createTable();
- readTable();
- return null;
- }
- });
+ startNewMiniCluster();
+
+ verifyAllowed(createTable(TABLE_NAME), superUser1);
+ verifyAllowed(readTable(TABLE_NAME), superUser1);
Set<String> tables = getHBaseTables();
- assertTrue("HBase tables do not include expected Phoenix tables: " + tables,
- tables.containsAll(PHOENIX_SYSTEM_TABLES));
+ if(isNamespaceMapped) {
+ assertTrue("HBase tables do not include expected Phoenix tables: " + tables,
+ tables.containsAll(PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES));
+ } else {
+ assertTrue("HBase tables do not include expected Phoenix tables: " + tables,
+ tables.containsAll(PHOENIX_SYSTEM_TABLES));
+ }
// Grant permission to the system tables for the unprivileged user
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
+ superUser1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
try {
- grantPermissions(regularUser.getShortUserName(), PHOENIX_SYSTEM_TABLES,
- Action.EXEC, Action.READ);
- grantPermissions(regularUser.getShortUserName(),
- Collections.singleton(TABLE_NAME), Action.READ);
- } catch (Throwable e) {
- if (e instanceof Exception) {
- throw (Exception) e;
+ if(isNamespaceMapped) {
+ grantPermissions(regularUser1.getShortName(),
+ PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES, Action.EXEC, Action.READ);
} else {
- throw new Exception(e);
+ grantPermissions(regularUser1.getShortName(), PHOENIX_SYSTEM_TABLES,
+ Action.EXEC, Action.READ);
}
- }
- return null;
- }
- });
-
- // Make sure that the unprivileged user can read the table
- regularUser.doAs(new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- // We expect this to not throw an error
- readTable();
- return null;
- }
- });
- }
-
- @Test
- public void testNamespaceMappedSystemTables() throws Exception {
- testUtil = new HBaseTestingUtility();
- clientProperties = new Properties();
- Configuration conf = testUtil.getConfiguration();
- setCommonConfigProperties(conf);
- testUtil.getConfiguration().set(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "true");
- clientProperties.setProperty(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "true");
- testUtil.startMiniCluster(1);
- final UserGroupInformation superUser =
- UserGroupInformation.createUserForTesting(SUPERUSER, new String[0]);
- final UserGroupInformation regularUser =
- UserGroupInformation.createUserForTesting("user", new String[0]);
-
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- createTable();
- readTable();
- return null;
- }
- });
-
- Set<String> tables = getHBaseTables();
- assertTrue("HBase tables do not include expected Phoenix tables: " + tables,
- tables.containsAll(PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES));
-
- // Grant permission to the system tables for the unprivileged user
- // An unprivileged user should only need to be able to Read and eXecute on them.
- superUser.doAs(new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- try {
- grantPermissions(regularUser.getShortUserName(),
- PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES, Action.EXEC, Action.READ);
- grantPermissions(regularUser.getShortUserName(),
+ grantPermissions(regularUser1.getShortName(),
Collections.singleton(TABLE_NAME), Action.READ);
} catch (Throwable e) {
if (e instanceof Exception) {
@@ -199,66 +82,7 @@ public class SystemTablePermissionsIT {
}
});
- regularUser.doAs(new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- // We expect this to not throw an error
- readTable();
- return null;
- }
- });
- }
-
- private String getJdbcUrl() {
- return "jdbc:phoenix:localhost:" + testUtil.getZkCluster().getClientPort() + ":/hbase";
- }
-
- private void createTable() throws SQLException {
- try (Connection conn = DriverManager.getConnection(getJdbcUrl(), clientProperties);
- Statement stmt = conn.createStatement();) {
- assertFalse(stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME));
- assertFalse(stmt.execute("CREATE TABLE " + TABLE_NAME
- + "(pk INTEGER not null primary key, data VARCHAR)"));
- try (PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO "
- + TABLE_NAME + " values(?, ?)")) {
- for (int i = 0; i < NUM_RECORDS; i++) {
- pstmt.setInt(1, i);
- pstmt.setString(2, Integer.toString(i));
- assertEquals(1, pstmt.executeUpdate());
- }
- }
- conn.commit();
- }
- }
-
- private void readTable() throws SQLException {
- try (Connection conn = DriverManager.getConnection(getJdbcUrl(), clientProperties);
- Statement stmt = conn.createStatement()) {
- ResultSet rs = stmt.executeQuery("SELECT pk, data FROM " + TABLE_NAME);
- assertNotNull(rs);
- int i = 0;
- while (rs.next()) {
- assertEquals(i, rs.getInt(1));
- assertEquals(Integer.toString(i), rs.getString(2));
- i++;
- }
- assertEquals(NUM_RECORDS, i);
- }
- }
-
- private void grantPermissions(String toUser, Set<String> tablesToGrant, Action... actions)
- throws Throwable {
- for (String table : tablesToGrant) {
- AccessControlClient.grant(testUtil.getConnection(), TableName.valueOf(table), toUser,
- null, null, actions);
- }
- }
-
- private Set<String> getHBaseTables() throws IOException {
- Set<String> tables = new HashSet<>();
- for (TableName tn : testUtil.getHBaseAdmin().listTableNames()) {
- tables.add(tn.getNameAsString());
- }
- return tables;
+ // Make sure that the unprivileged user can now read the table
+ verifyAllowed(readTable(TABLE_NAME), regularUser1);
}
}