/** * 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.hive.jdbc.authorization; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.ql.security.SessionStateUserAuthenticator; import org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdHiveAuthorizerFactory; import org.apache.hive.jdbc.miniHS2.MiniHS2; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; /** * Test SQL standard authorization with jdbc/hiveserver2 */ public class TestJdbcWithSQLAuthorization { private static MiniHS2 miniHS2 = null; @BeforeClass public static void beforeTest() throws Exception { Class.forName(MiniHS2.getJdbcDriverName()); HiveConf conf = new HiveConf(); conf.setVar(ConfVars.HIVE_AUTHORIZATION_MANAGER, SQLStdHiveAuthorizerFactory.class.getName()); conf.setVar(ConfVars.HIVE_AUTHENTICATOR_MANAGER, SessionStateUserAuthenticator.class.getName()); conf.setBoolVar(ConfVars.HIVE_AUTHORIZATION_ENABLED, true); conf.setBoolVar(ConfVars.HIVE_SUPPORT_CONCURRENCY, false); conf.setBoolVar(ConfVars.HIVE_SERVER2_ENABLE_DOAS, false); miniHS2 = new MiniHS2(conf); miniHS2.start(new HashMap<String, String>()); } @AfterClass public static void afterTest() throws Exception { if (miniHS2.isStarted()) { miniHS2.stop(); } } @Test public void testAuthorization1() throws Exception { String tableName1 = "test_jdbc_sql_auth1"; String tableName2 = "test_jdbc_sql_auth2"; // using different code blocks so that jdbc variables are not accidently re-used // between the actions. Different connection/statement object should be used for each action. { // create tables as user1 Connection hs2Conn = getConnection("user1"); Statement stmt = hs2Conn.createStatement(); // create tables stmt.execute("create table " + tableName1 + "(i int) "); stmt.execute("create table " + tableName2 + "(i int) "); stmt.close(); hs2Conn.close(); } { // try dropping table as user1 - should succeed Connection hs2Conn = getConnection("user1"); Statement stmt = hs2Conn.createStatement(); stmt.execute("drop table " + tableName1); } { // try using jdbc metadata api to get column list as user2 - should fail Connection hs2Conn = getConnection("user2"); try { hs2Conn.getMetaData().getColumns(null, "default", tableName2, null); fail("Exception due to authorization failure is expected"); } catch (SQLException e) { String msg = e.getMessage(); // check parts of the error, not the whole string so as not to tightly // couple the error message with test System.err.println("Got SQLException with message " + msg); assertTrue("Checking permission denied error", msg.contains("user2")); assertTrue("Checking permission denied error", msg.contains(tableName2)); assertTrue("Checking permission denied error", msg.contains("SELECT")); } } { // try dropping table as user2 - should fail Connection hs2Conn = getConnection("user2"); try { Statement stmt = hs2Conn.createStatement(); stmt.execute("drop table " + tableName2); fail("Exception due to authorization failure is expected"); } catch (SQLException e) { String msg = e.getMessage(); System.err.println("Got SQLException with message " + msg); // check parts of the error, not the whole string so as not to tightly // couple the error message with test assertTrue("Checking permission denied error", msg.contains("user2")); assertTrue("Checking permission denied error", msg.contains(tableName2)); assertTrue("Checking permission denied error", msg.contains("OBJECT OWNERSHIP")); } } } private Connection getConnection(String userName) throws Exception { return DriverManager.getConnection(miniHS2.getJdbcURL(), userName, "bar"); } @Test public void testAllowedCommands() throws Exception { // using different code blocks so that jdbc variables are not accidently re-used // between the actions. Different connection/statement object should be used for each action. { // create tables as user1 Connection hs2Conn = getConnection("user1"); boolean caughtException = false; Statement stmt = hs2Conn.createStatement(); // create tables try { stmt.execute("dfs -ls /tmp/"); } catch (SQLException e) { caughtException = true; String msg = "Permission denied: Principal [name=user1, type=USER] does not have " + "following privileges for operation DFS [[ADMIN PRIVILEGE] on " + "Object [type=COMMAND_PARAMS, name=[-ls, /tmp/]]]"; assertTrue("Checking content of error message:" + e.getMessage(), e.getMessage().contains(msg)); } finally { stmt.close(); hs2Conn.close(); } assertTrue("Exception expected ", caughtException); } } @Test public void testBlackListedUdfUsage() throws Exception { // create tables as user1 Connection hs2Conn = getConnection("user1"); Statement stmt = hs2Conn.createStatement(); String tableName1 = "test_jdbc_sql_auth_udf"; stmt.execute("create table " + tableName1 + "(i int) "); verifyUDFNotAllowed(stmt, tableName1, "reflect('java.lang.String', 'valueOf', 1)", "reflect"); verifyUDFNotAllowed(stmt, tableName1, "reflect2('java.lang.String', 'valueOf', 1)", "reflect2"); verifyUDFNotAllowed(stmt, tableName1, "java_method('java.lang.String', 'valueOf', 1)", "java_method"); stmt.close(); hs2Conn.close(); } private void verifyUDFNotAllowed(Statement stmt, String tableName, String udfcall, String udfname) { try { stmt.execute("SELECT " + udfcall + " from " + tableName); fail("Disallowed udf usage should have resulted in error"); } catch (SQLException e) { checkAssertContains("UDF " + udfname + " is not allowed", e.getMessage()); } } private void checkAssertContains(String expectedSubString, String message) { if (message.contains(expectedSubString)) { return; } fail("Message [" + message + "] does not contain substring [" + expectedSubString + "]"); } @Test public void testConfigWhiteList() throws Exception { // create tables as user1 Connection hs2Conn = getConnection("user1"); Statement stmt = hs2Conn.createStatement(); try { stmt.execute("set hive.metastore.uris=x"); fail("exception expected"); } catch (SQLException e) { String msg = "Cannot modify hive.metastore.uris at runtime. " + "It is not in list of params that are allowed to be modified at runtime"; assertTrue(e.getMessage().contains(msg)); } stmt.execute("set hive.exec.reducers.bytes.per.reducer=10000"); //no exception should be thrown stmt.close(); hs2Conn.close(); } }