ACCESS-208: Add interface for on failure hooks
diff --git a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHook.java b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHook.java
new file mode 100644
index 0000000..776eb6a
--- /dev/null
+++ b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHook.java
@@ -0,0 +1,38 @@
+/**
+ * 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.access.binding.hive;
+
+import org.apache.hadoop.hive.ql.hooks.Hook;
+
+/**
+ *
+ * AccessOnFailureHook allows Access to be extended
+ * with custom logic to be executed upon authorization failure.
+ *
+ */
+public interface AccessOnFailureHook extends Hook {
+
+ /**
+ *
+ * @param context
+ * The hook context passed to each hook.
+ * @throws Exception
+ */
+ void run(AccessOnFailureHookContext context) throws Exception;
+}
diff --git a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContext.java b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContext.java
new file mode 100644
index 0000000..df9b0ba
--- /dev/null
+++ b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContext.java
@@ -0,0 +1,89 @@
+/*
+ * 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.access.binding.hive;
+
+import org.apache.access.core.AccessURI;
+import org.apache.access.core.Database;
+import org.apache.access.core.Table;
+import org.apache.hadoop.hive.ql.exec.Task;
+import org.apache.hadoop.hive.ql.hooks.ReadEntity;
+import org.apache.hadoop.hive.ql.hooks.WriteEntity;
+import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Context information provided by Access to implementations
+ * of AccessOnFailureHook
+ */
+public interface AccessOnFailureHookContext {
+
+ /**
+ * @return the command attempted by user
+ */
+ public String getCommand();
+
+ /**
+ * @return the set of read entities
+ */
+ public Set<ReadEntity> getInputs();
+
+ /**
+ * @return the set of write entities
+ */
+ public Set<WriteEntity> getOutputs();
+
+ /**
+ * @return the user name
+ */
+ public String getUserName();
+
+ /**
+ * @return the ip address
+ */
+ public String getIpAddress();
+
+ /**
+ * @return the database object
+ */
+ public Database getDatabase();
+
+ /**
+ * @return the table object
+ */
+ public Table getTable();
+
+ /**
+ * @return the udf URI
+ */
+ public AccessURI getUdfURI();
+
+ /**
+ * @return the partition URI
+ */
+ public AccessURI getPartitionURI();
+
+ /**
+ * @return the authorization failure exception
+ */
+ public AuthorizationException getException();
+
+}
\ No newline at end of file
diff --git a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContextImpl.java b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContextImpl.java
new file mode 100644
index 0000000..8e4190f
--- /dev/null
+++ b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/AccessOnFailureHookContextImpl.java
@@ -0,0 +1,111 @@
+/*
+ * 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.access.binding.hive;
+
+import org.apache.access.core.AccessURI;
+import org.apache.access.core.Database;
+import org.apache.access.core.Table;
+import org.apache.hadoop.hive.ql.exec.Task;
+import org.apache.hadoop.hive.ql.hooks.ReadEntity;
+import org.apache.hadoop.hive.ql.hooks.WriteEntity;
+import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+public class AccessOnFailureHookContextImpl implements AccessOnFailureHookContext {
+
+ private final String command;
+ private final Set<ReadEntity> inputs;
+ private final Set<WriteEntity> outputs;
+ private final String userName;
+ private final String ipAddress;
+ private final Database database;
+ private final Table table;
+ private final AccessURI udfURI;
+ private final AccessURI partitionURI;
+ private final AuthorizationException authException;
+
+ public AccessOnFailureHookContextImpl(String command,
+ Set<ReadEntity> inputs, Set<WriteEntity> outputs, Database db,
+ Table tab, AccessURI udfURI, AccessURI partitionURI,
+ String userName, String ipAddress, AuthorizationException e) {
+ this.command = command;
+ this.inputs = inputs;
+ this.outputs = outputs;
+ this.userName = userName;
+ this.ipAddress = ipAddress;
+ this.database = db;
+ this.table = tab;
+ this.udfURI = udfURI;
+ this.partitionURI = partitionURI;
+ this.authException = e;
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public Set<ReadEntity> getInputs() {
+ return inputs;
+ }
+
+ @Override
+ public Set<WriteEntity> getOutputs() {
+ return outputs;
+ }
+
+ @Override
+ public String getUserName() {
+ return userName;
+ }
+
+ @Override
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ @Override
+ public Database getDatabase() {
+ return database;
+ }
+
+ @Override
+ public Table getTable() {
+ return table;
+ }
+
+ @Override
+ public AccessURI getUdfURI() {
+ return udfURI;
+ }
+
+ @Override
+ public AccessURI getPartitionURI() {
+ return partitionURI;
+ }
+
+ @Override
+ public AuthorizationException getException() {
+ return authException;
+ }
+}
\ No newline at end of file
diff --git a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/HiveAuthzBindingHook.java b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/HiveAuthzBindingHook.java
index 681f3aa..7a224ea 100644
--- a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/HiveAuthzBindingHook.java
+++ b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/HiveAuthzBindingHook.java
@@ -42,6 +42,7 @@
import org.apache.access.core.Database;
import org.apache.access.core.Subject;
import org.apache.access.core.Table;
+import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.ql.HiveDriverFilterHook;
@@ -51,6 +52,7 @@
import org.apache.hadoop.hive.ql.exec.Task;
import org.apache.hadoop.hive.ql.hooks.Entity;
import org.apache.hadoop.hive.ql.hooks.Entity.Type;
+import org.apache.hadoop.hive.ql.hooks.Hook;
import org.apache.hadoop.hive.ql.hooks.ReadEntity;
import org.apache.hadoop.hive.ql.hooks.WriteEntity;
import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
@@ -270,11 +272,27 @@
try {
authorizeWithHiveBindings(context, stmtAuthObject, stmtOperation);
} catch (AuthorizationException e) {
+ executeOnFailureHooks(context, e);
throw new SemanticException("No valid privileges", e);
}
hiveAuthzBinding.set(context.getConf());
}
+ private void executeOnFailureHooks(HiveSemanticAnalyzerHookContext context,
+ AuthorizationException e) {
+ AccessOnFailureHookContext hookCtx = new AccessOnFailureHookContextImpl(
+ context.getCommand(), context.getInputs(), context.getOutputs(),
+ currDB, currTab, udfURI, partitionURI, context.getUserName(),
+ context.getIpAddress(), e);
+ try {
+ for (Hook aofh : getHooks(HiveAuthzConf.AuthzConfVars.AUTHZ_ONFAILURE_HOOKS)) {
+ ((AccessOnFailureHook)aofh).run(hookCtx);
+ }
+ } catch (Exception ex) {
+ LOG.error("Error executing hook:", ex);
+ }
+ }
+
/**
* Convert the input/output entities into authorizables. generate
* authorizables for cases like Database and metadata operations where the
@@ -626,4 +644,57 @@
return false;
}
}
-}
+
+ /**
+ * Returns a set of hooks specified in a configuration variable.
+ *
+ * See getHooks(HiveAuthzConf.AuthzConfVars hookConfVar, Class<T> clazz)
+ * @param hookConfVar
+ * @return
+ * @throws Exception
+ */
+ private List<Hook> getHooks(HiveAuthzConf.AuthzConfVars hookConfVar) throws Exception {
+ return getHooks(hookConfVar, Hook.class);
+ }
+
+ /**
+ * Returns the hooks specified in a configuration variable. The hooks are returned in a list in
+ * the order they were specified in the configuration variable.
+ *
+ * @param hookConfVar The configuration variable specifying a comma separated list of the hook
+ * class names.
+ * @param clazz The super type of the hooks.
+ * @return A list of the hooks cast as the type specified in clazz, in the order
+ * they are listed in the value of hookConfVar
+ * @throws Exception
+ */
+ private <T extends Hook> List<T> getHooks(HiveAuthzConf.AuthzConfVars hookConfVar, Class<T> clazz)
+ throws Exception {
+
+ List<T> hooks = new ArrayList<T>();
+ String csHooks = authzConf.get(hookConfVar.getVar(), "");
+ if (csHooks == null) {
+ return hooks;
+ }
+
+ csHooks = csHooks.trim();
+ if (csHooks.equals("")) {
+ return hooks;
+ }
+
+ String[] hookClasses = csHooks.split(",");
+
+ for (String hookClass : hookClasses) {
+ try {
+ T hook =
+ (T) Class.forName(hookClass.trim(), true, JavaUtils.getClassLoader()).newInstance();
+ hooks.add(hook);
+ } catch (ClassNotFoundException e) {
+ LOG.error(hookConfVar.getVar() + " Class not found:" + e.getMessage());
+ throw e;
+ }
+ }
+
+ return hooks;
+ }
+}
\ No newline at end of file
diff --git a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/conf/HiveAuthzConf.java b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/conf/HiveAuthzConf.java
index 59650bf..dd536f6 100644
--- a/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/conf/HiveAuthzConf.java
+++ b/access-binding/access-binding-hive/src/main/java/org/apache/access/binding/hive/conf/HiveAuthzConf.java
@@ -46,6 +46,7 @@
ACCESS_TESTING_MODE("hive.access.testing.mode", "false"),
AUTHZ_UDF_WHITELIST("hive.access.udf.whitelist", HIVE_UDF_WHITE_LIST),
AUTHZ_ALLOW_HIVE_IMPERSONATION("hive.access.allow.hive.impersonation", "false"),
+ AUTHZ_ONFAILURE_HOOKS("hive.access.failure.hooks", ""),
;
private final String varName;
@@ -138,4 +139,4 @@
}
return retVal;
}
-}
\ No newline at end of file
+}
diff --git a/access-tests/src/test/java/org/apache/access/tests/e2e/DummyAccessOnFailureHook.java b/access-tests/src/test/java/org/apache/access/tests/e2e/DummyAccessOnFailureHook.java
new file mode 100644
index 0000000..6cfbe84
--- /dev/null
+++ b/access-tests/src/test/java/org/apache/access/tests/e2e/DummyAccessOnFailureHook.java
@@ -0,0 +1,32 @@
+/*
+ * 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.access.tests.e2e;
+
+import org.apache.access.binding.hive.AccessOnFailureHook;
+import org.apache.access.binding.hive.AccessOnFailureHookContext;
+
+public class DummyAccessOnFailureHook implements AccessOnFailureHook {
+
+ static boolean invoked = false;
+
+ @Override
+ public void run(AccessOnFailureHookContext failureHookContext)
+ throws Exception {
+ invoked = true;
+ }
+}
diff --git a/access-tests/src/test/java/org/apache/access/tests/e2e/TestAccessOnFailureHookLoading.java b/access-tests/src/test/java/org/apache/access/tests/e2e/TestAccessOnFailureHookLoading.java
new file mode 100644
index 0000000..d6a70f5
--- /dev/null
+++ b/access-tests/src/test/java/org/apache/access/tests/e2e/TestAccessOnFailureHookLoading.java
@@ -0,0 +1,133 @@
+/*
+ * 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.access.tests.e2e;
+
+import com.google.common.io.Resources;
+import org.apache.access.binding.hive.conf.HiveAuthzConf;
+import org.apache.access.tests.e2e.hiveserver.HiveServerFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+import junit.framework.Assert;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+public class TestAccessOnFailureHookLoading extends AbstractTestWithHiveServer {
+
+ private Context context;
+ Map<String, String > testProperties;
+ private static final String SINGLE_TYPE_DATA_FILE_NAME = "kv1.dat";
+
+ @Before
+ public void setup() throws Exception {
+ testProperties = new HashMap<String, String>();
+ testProperties.put(HiveAuthzConf.AuthzConfVars.AUTHZ_ONFAILURE_HOOKS.getVar(),
+ DummyAccessOnFailureHook.class.getName());
+ }
+
+ @After
+ public void teardown() throws Exception {
+ if (context != null) {
+ context.close();
+ }
+ }
+
+ /* Admin creates database DB_2
+ * USER_1 tries to drop DB_2, but it has permissions for DB_1.
+ */
+ @Test
+ public void testOnFailureHookLoading() throws Exception {
+
+ // Do not run this test if run with external HiveServer2
+ // This test checks for a static member, which will not
+ // be set if HiveServer2 and the test run in different JVMs
+ String hiveServer2Type = System.getProperty(
+ HiveServerFactory.HIVESERVER2_TYPE);
+ if (hiveServer2Type != null &&
+ HiveServerFactory.HiveServer2Type.valueOf(hiveServer2Type.trim()) !=
+ HiveServerFactory.HiveServer2Type.InternalHiveServer2) {
+ return;
+ }
+
+ context = createContext(testProperties);
+
+ File policyFile = context.getPolicyFile();
+ File dataDir = context.getDataDir();
+ //copy data file to test dir
+ File dataFile = new File(dataDir, SINGLE_TYPE_DATA_FILE_NAME);
+ FileOutputStream to = new FileOutputStream(dataFile);
+ Resources.copy(Resources.getResource(SINGLE_TYPE_DATA_FILE_NAME), to);
+ to.close();
+ //delete existing policy file; create new policy file
+ assertTrue("Could not delete " + policyFile, context.deletePolicyFile());
+ // groups : role -> group
+ context.append("[groups]");
+ context.append("admin = all_server");
+ context.append("user_group1 = all_db1, load_data");
+ // roles: privileges -> role
+ context.append("[roles]");
+ context.append("all_server = server=server1");
+ context.append("all_db1 = server=server1->db=DB_1");
+ // users: users -> groups
+ context.append("[users]");
+ context.append("hive = admin");
+ context.append("user_1 = user_group1");
+ // setup db objects needed by the test
+ Connection connection = context.createConnection("hive", "hive");
+ Statement statement = context.createStatement(connection);
+ statement.execute("DROP DATABASE IF EXISTS DB_1 CASCADE");
+ statement.execute("DROP DATABASE IF EXISTS DB_2 CASCADE");
+ statement.execute("CREATE DATABASE DB_1");
+ statement.execute("CREATE DATABASE DB_2");
+ statement.close();
+ connection.close();
+
+ // test execution
+ connection = context.createConnection("user_1", "password");
+ statement = context.createStatement(connection);
+
+ //negative test case: user can't drop another user's database
+ assertFalse(DummyAccessOnFailureHook.invoked);
+ try {
+ statement.execute("DROP DATABASE DB_2 CASCADE");
+ Assert.fail("Expected SQL exception");
+ } catch (SQLException e) {
+ assertTrue(DummyAccessOnFailureHook.invoked);
+ }
+
+ statement.close();
+ connection.close();
+
+ //test cleanup
+ connection = context.createConnection("hive", "hive");
+ statement = context.createStatement(connection);
+ statement.execute("DROP DATABASE DB_1 CASCADE");
+ statement.execute("DROP DATABASE DB_2 CASCADE");
+ statement.close();
+ connection.close();
+ context.close();
+ }
+}
diff --git a/access-tests/src/test/java/org/apache/access/tests/e2e/hiveserver/HiveServerFactory.java b/access-tests/src/test/java/org/apache/access/tests/e2e/hiveserver/HiveServerFactory.java
index 873176c..08b0fd9 100644
--- a/access-tests/src/test/java/org/apache/access/tests/e2e/hiveserver/HiveServerFactory.java
+++ b/access-tests/src/test/java/org/apache/access/tests/e2e/hiveserver/HiveServerFactory.java
@@ -24,6 +24,7 @@
import java.net.URL;
import java.util.Map;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.access.binding.hive.conf.HiveAuthzConf;
import org.apache.access.provider.file.LocalGroupResourceAuthorizationProvider;
import org.apache.hadoop.fs.FileSystem;
@@ -196,7 +197,8 @@
return port;
}
- private static enum HiveServer2Type {
+ @VisibleForTesting
+ public static enum HiveServer2Type {
EmbeddedHiveServer2, // Embedded HS2, directly executed by JDBC, without thrift
InternalHiveServer2, // Start a thrift HS2 in the same process
ExternalHiveServer2, // start a remote thrift HS2