blob: 3965f699b72d0277c723d70c0f6c0d8deacc1e67 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.phoenix.end2end;
import 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.apache.phoenix.util.SchemaUtil;
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, surroundWithDoubleQuotes(SchemaUtil.SCHEMA_FOR_DEFAULT_NAMESPACE), 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("RX", 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("ARX", 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("RX", 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, surroundWithDoubleQuotes(SchemaUtil.SCHEMA_FOR_DEFAULT_NAMESPACE), 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("RX", 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);
}
/**
* Grant RX permissions on the schema to regularUser1,
* Creating view on a table with that schema by regularUser1 should be allowed
*/
@Test
public void testCreateViewOnTableWithRXPermsOnSchema() throws Exception {
startNewMiniCluster();
grantSystemTableAccess(superUser1, regularUser1, regularUser2, regularUser3);
if(isNamespaceMapped) {
verifyAllowed(createSchema(SCHEMA_NAME), superUser1);
verifyAllowed(createTable(FULL_TABLE_NAME), superUser1);
verifyAllowed(grantPermissions("RX", regularUser1, SCHEMA_NAME, true), superUser1);
} else {
verifyAllowed(createTable(FULL_TABLE_NAME), superUser1);
verifyAllowed(grantPermissions("RX", regularUser1, surroundWithDoubleQuotes(SchemaUtil.SCHEMA_FOR_DEFAULT_NAMESPACE), true), superUser1);
}
verifyAllowed(createView(VIEW1_TABLE_NAME, FULL_TABLE_NAME), regularUser1);
}
}