org.apache.drill.exec.impersonation.TestImpersonationMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.drill.exec.impersonation.TestImpersonationMetadata.java

Source

/**
 * 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.drill.exec.impersonation;

import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.exceptions.UserRemoteException;
import org.apache.drill.exec.store.dfs.WorkspaceConfig;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Map;

import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

/**
 * Tests impersonation on metadata related queries as SHOW FILES, SHOW TABLES, CREATE VIEW, CREATE TABLE and DROP TABLE
 */
public class TestImpersonationMetadata extends BaseTestImpersonation {
    private static final String user1 = "drillTestUser1";
    private static final String user2 = "drillTestUser2";

    private static final String group0 = "drillTestGrp0";
    private static final String group1 = "drillTestGrp1";

    static {
        UserGroupInformation.createUserForTesting(user1, new String[] { group1, group0 });
        UserGroupInformation.createUserForTesting(user2, new String[] { group1 });
    }

    @BeforeClass
    public static void setup() throws Exception {
        startMiniDfsCluster(TestImpersonationMetadata.class.getSimpleName());
        startDrillCluster(true);
        addMiniDfsBasedStorage(createTestWorkspaces());
    }

    private static Map<String, WorkspaceConfig> createTestWorkspaces() throws Exception {
        // Create "/tmp" folder and set permissions to "777"
        final Path tmpPath = new Path("/tmp");
        fs.delete(tmpPath, true);
        FileSystem.mkdirs(fs, tmpPath, new FsPermission((short) 0777));

        Map<String, WorkspaceConfig> workspaces = Maps.newHashMap();

        // Create /drillTestGrp0_700 directory with permissions 700 (owned by user running the tests)
        createAndAddWorkspace("drillTestGrp0_700", "/drillTestGrp0_700", (short) 0700, processUser, group0,
                workspaces);

        // Create /drillTestGrp0_750 directory with permissions 750 (owned by user running the tests)
        createAndAddWorkspace("drillTestGrp0_750", "/drillTestGrp0_750", (short) 0750, processUser, group0,
                workspaces);

        // Create /drillTestGrp0_755 directory with permissions 755 (owned by user running the tests)
        createAndAddWorkspace("drillTestGrp0_755", "/drillTestGrp0_755", (short) 0755, processUser, group0,
                workspaces);

        // Create /drillTestGrp0_770 directory with permissions 770 (owned by user running the tests)
        createAndAddWorkspace("drillTestGrp0_770", "/drillTestGrp0_770", (short) 0770, processUser, group0,
                workspaces);

        // Create /drillTestGrp0_777 directory with permissions 777 (owned by user running the tests)
        createAndAddWorkspace("drillTestGrp0_777", "/drillTestGrp0_777", (short) 0777, processUser, group0,
                workspaces);

        // Create /drillTestGrp1_700 directory with permissions 700 (owned by user1)
        createAndAddWorkspace("drillTestGrp1_700", "/drillTestGrp1_700", (short) 0700, user1, group1, workspaces);

        // create /user2_workspace1 with 775 permissions (owner by user1)
        createAndAddWorkspace("user2_workspace1", "/user2_workspace1", (short) 0775, user2, group1, workspaces);

        // create /user2_workspace with 755 permissions (owner by user1)
        createAndAddWorkspace("user2_workspace2", "/user2_workspace2", (short) 0755, user2, group1, workspaces);

        return workspaces;
    }

    @Test
    public void testDropTable() throws Exception {

        // create tables as user2
        updateClient(user2);
        test(String.format("use `%s.user2_workspace1`", MINIDFS_STORAGE_PLUGIN_NAME));
        // create a table that can be dropped by another user in a different group
        test("create table parquet_table_775 as select * from cp.`employee.json`");

        // create a table that cannot be dropped by another user
        test(String.format("use `%s.user2_workspace2`", MINIDFS_STORAGE_PLUGIN_NAME));
        test("create table parquet_table_700 as select * from cp.`employee.json`");

        // Drop tables as user1
        updateClient(user1);
        test(String.format("use `%s.user2_workspace1`", MINIDFS_STORAGE_PLUGIN_NAME));
        testBuilder().sqlQuery("drop table parquet_table_775").unOrdered().baselineColumns("ok", "summary")
                .baselineValues(true, String.format("Table [%s] dropped", "parquet_table_775")).go();

        test(String.format("use `%s.user2_workspace2`", MINIDFS_STORAGE_PLUGIN_NAME));
        boolean dropFailed = false;
        try {
            test("drop table parquet_table_700");
        } catch (UserException e) {
            Assert.assertTrue(e.getMessage().contains("PERMISSION ERROR"));
            dropFailed = true;
        }
        Assert.assertTrue("Permission checking failed during drop table", dropFailed);
    }

    @Test // DRILL-3037
    public void testImpersonatingProcessUser() throws Exception {
        updateClient(processUser);

        // Process user start the mini dfs, he has read/write permissions by default
        final String viewName = String.format("%s.drillTestGrp0_700.testView", MINIDFS_STORAGE_PLUGIN_NAME);
        try {
            test("CREATE VIEW " + viewName + " AS SELECT * FROM cp.`region.json`");
            test("SELECT * FROM " + viewName + " LIMIT 2");
        } finally {
            test("DROP VIEW " + viewName);
        }
    }

    @Test
    public void testShowFilesInWSWithUserAndGroupPermissionsForQueryUser() throws Exception {
        updateClient(user1);

        // Try show tables in schema "drillTestGrp1_700" which is owned by "user1"
        test(String.format("SHOW FILES IN %s.drillTestGrp1_700", MINIDFS_STORAGE_PLUGIN_NAME));

        // Try show tables in schema "drillTestGrp0_750" which is owned by "processUser" and has group permissions for
        // "user1"
        test(String.format("SHOW FILES IN %s.drillTestGrp0_750", MINIDFS_STORAGE_PLUGIN_NAME));
    }

    @Test
    public void testShowFilesInWSWithOtherPermissionsForQueryUser() throws Exception {
        updateClient(user2);
        // Try show tables in schema "drillTestGrp0_755" which is owned by "processUser" and group0. "user2" is not part
        // of the "group0"
        test(String.format("SHOW FILES IN %s.drillTestGrp0_755", MINIDFS_STORAGE_PLUGIN_NAME));
    }

    @Test
    public void testShowFilesInWSWithNoPermissionsForQueryUser() throws Exception {
        UserRemoteException ex = null;

        updateClient(user2);
        try {
            // Try show tables in schema "drillTestGrp1_700" which is owned by "user1"
            test(String.format("SHOW FILES IN %s.drillTestGrp1_700", MINIDFS_STORAGE_PLUGIN_NAME));
        } catch (UserRemoteException e) {
            ex = e;
        }

        assertNotNull("UserRemoteException is expected", ex);
        assertThat(ex.getMessage(), containsString("Permission denied: user=drillTestUser2, "
                + "access=READ_EXECUTE, inode=\"/drillTestGrp1_700\":drillTestUser1:drillTestGrp1:drwx------"));
    }

    @Test
    public void testShowSchemasAsUser1() throws Exception {
        // "user1" is part of "group0" and has access to following workspaces
        // drillTestGrp1_700 (through ownership)
        // drillTestGrp0_750, drillTestGrp0_770 (through "group" category permissions)
        // drillTestGrp0_755, drillTestGrp0_777 (through "others" category permissions)
        updateClient(user1);
        testBuilder().sqlQuery("SHOW SCHEMAS LIKE '%drillTest%'").unOrdered().baselineColumns("SCHEMA_NAME")
                .baselineValues(String.format("%s.drillTestGrp0_750", MINIDFS_STORAGE_PLUGIN_NAME))
                .baselineValues(String.format("%s.drillTestGrp0_755", MINIDFS_STORAGE_PLUGIN_NAME))
                .baselineValues(String.format("%s.drillTestGrp0_770", MINIDFS_STORAGE_PLUGIN_NAME))
                .baselineValues(String.format("%s.drillTestGrp0_777", MINIDFS_STORAGE_PLUGIN_NAME))
                .baselineValues(String.format("%s.drillTestGrp1_700", MINIDFS_STORAGE_PLUGIN_NAME)).go();
    }

    @Test
    public void testShowSchemasAsUser2() throws Exception {
        // "user2" is part of "group0", but part of "group1" and has access to following workspaces
        // drillTestGrp0_755, drillTestGrp0_777 (through "others" category permissions)
        updateClient(user2);
        testBuilder().sqlQuery("SHOW SCHEMAS LIKE '%drillTest%'").unOrdered().baselineColumns("SCHEMA_NAME")
                .baselineValues(String.format("%s.drillTestGrp0_755", MINIDFS_STORAGE_PLUGIN_NAME))
                .baselineValues(String.format("%s.drillTestGrp0_777", MINIDFS_STORAGE_PLUGIN_NAME)).go();
    }

    @Test
    public void testCreateViewInDirWithUserPermissionsForQueryUser() throws Exception {
        final String viewSchema = MINIDFS_STORAGE_PLUGIN_NAME + ".drillTestGrp1_700"; // Workspace dir owned by "user1"
        testCreateViewTestHelper(user1, viewSchema, "view1");
    }

    @Test
    public void testCreateViewInDirWithGroupPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user1" is part of "group0"
        final String viewSchema = MINIDFS_STORAGE_PLUGIN_NAME + ".drillTestGrp0_770";
        testCreateViewTestHelper(user1, viewSchema, "view1");
    }

    @Test
    public void testCreateViewInDirWithOtherPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user2" is not part of "group0"
        final String viewSchema = MINIDFS_STORAGE_PLUGIN_NAME + ".drillTestGrp0_777";
        testCreateViewTestHelper(user2, viewSchema, "view1");
    }

    private static void testCreateViewTestHelper(String user, String viewSchema, String viewName) throws Exception {
        try {
            updateClient(user);

            test("USE " + viewSchema);

            test("CREATE VIEW " + viewName + " AS SELECT "
                    + "c_custkey, c_nationkey FROM cp.`tpch/customer.parquet` ORDER BY c_custkey;");

            testBuilder().sqlQuery("SHOW TABLES").unOrdered().baselineColumns("TABLE_SCHEMA", "TABLE_NAME")
                    .baselineValues(viewSchema, viewName).go();

            test("SHOW FILES");

            testBuilder().sqlQuery("SELECT * FROM " + viewName + " LIMIT 1").ordered()
                    .baselineColumns("c_custkey", "c_nationkey").baselineValues(1, 15).go();

        } finally {
            test("DROP VIEW " + viewSchema + "." + viewName);
        }
    }

    @Test
    public void testCreateViewInWSWithNoPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user2" is not part of "group0"
        final String viewSchema = MINIDFS_STORAGE_PLUGIN_NAME + ".drillTestGrp0_755";
        final String viewName = "view1";

        updateClient(user2);

        test("USE " + viewSchema);

        final String query = "CREATE VIEW " + viewName + " AS SELECT "
                + "c_custkey, c_nationkey FROM cp.`tpch/customer.parquet` ORDER BY c_custkey;";
        final String expErrorMsg = "PERMISSION ERROR: Permission denied: user=drillTestUser2, access=WRITE, inode=\"/drillTestGrp0_755/";
        errorMsgTestHelper(query, expErrorMsg);

        // SHOW TABLES is expected to return no records as view creation fails above.
        testBuilder().sqlQuery("SHOW TABLES").expectsEmptyResultSet().go();

        test("SHOW FILES");
    }

    @Test
    public void testCreateTableInDirWithUserPermissionsForQueryUser() throws Exception {
        final String tableWS = "drillTestGrp1_700"; // Workspace dir owned by "user1"
        testCreateTableTestHelper(user1, tableWS, "table1");
    }

    @Test
    public void testCreateTableInDirWithGroupPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user1" is part of "group0"
        final String tableWS = "drillTestGrp0_770";
        testCreateTableTestHelper(user1, tableWS, "table1");
    }

    @Test
    public void testCreateTableInDirWithOtherPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user2" is not part of "group0"
        final String tableWS = "drillTestGrp0_777";
        testCreateTableTestHelper(user2, tableWS, "table1");
    }

    private static void testCreateTableTestHelper(String user, String tableWS, String tableName) throws Exception {
        try {
            updateClient(user);

            test("USE " + Joiner.on(".").join(MINIDFS_STORAGE_PLUGIN_NAME, tableWS));

            test("CREATE TABLE " + tableName + " AS SELECT "
                    + "c_custkey, c_nationkey FROM cp.`tpch/customer.parquet` ORDER BY c_custkey;");

            test("SHOW FILES");

            testBuilder().sqlQuery("SELECT * FROM " + tableName + " LIMIT 1").ordered()
                    .baselineColumns("c_custkey", "c_nationkey").baselineValues(1, 15).go();

        } finally {
            // There is no drop table, we need to delete the table directory through FileSystem object
            final Path tablePath = new Path(Path.SEPARATOR + tableWS + Path.SEPARATOR + tableName);
            if (fs.exists(tablePath)) {
                fs.delete(tablePath, true);
            }
        }
    }

    @Test
    public void testCreateTableInWSWithNoPermissionsForQueryUser() throws Exception {
        // Workspace dir owned by "processUser", workspace group is "group0" and "user2" is not part of "group0"
        final String tableWS = "drillTestGrp0_755";
        final String tableName = "table1";

        UserRemoteException ex = null;

        try {
            updateClient(user2);

            test("USE " + Joiner.on(".").join(MINIDFS_STORAGE_PLUGIN_NAME, tableWS));

            test("CREATE TABLE " + tableName + " AS SELECT "
                    + "c_custkey, c_nationkey FROM cp.`tpch/customer.parquet` ORDER BY c_custkey;");
        } catch (UserRemoteException e) {
            ex = e;
        }

        assertNotNull("UserRemoteException is expected", ex);
        assertThat(ex.getMessage(), containsString(
                "SYSTEM ERROR: RemoteException: Permission denied: user=drillTestUser2, access=WRITE, inode=\"/drillTestGrp0_755/"));
    }

    @Test
    public void testRefreshMetadata() throws Exception {
        final String tableName = "nation1";
        final String tableWS = "drillTestGrp1_700";

        updateClient(user1);
        test("USE " + Joiner.on(".").join(MINIDFS_STORAGE_PLUGIN_NAME, tableWS));

        test("CREATE TABLE " + tableName + " partition by (n_regionkey) AS SELECT * "
                + "FROM cp.`tpch/nation.parquet`;");

        test("refresh table metadata " + tableName + ";");

        test("SELECT * FROM " + tableName + ";");

        final Path tablePath = new Path(Path.SEPARATOR + tableWS + Path.SEPARATOR + tableName);
        assertTrue(fs.exists(tablePath) && fs.isDirectory(tablePath));
        fs.mkdirs(new Path(tablePath, "tmp5"));

        test("SELECT * from " + tableName + ";");

    }

    @AfterClass
    public static void removeMiniDfsBasedStorage() throws Exception {
        getDrillbitContext().getStorage().deletePlugin(MINIDFS_STORAGE_PLUGIN_NAME);
        stopMiniDfsCluster();
    }
}