http://git-wip-us.apache.org/repos/asf/phoenix/blob/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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 19b02d5..aeb6db3 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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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 8cdbba8..7fe08a9 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/f94f4eb1/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/f94f4eb1/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/f94f4eb1/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" +