ACCESS-217. Support for URIs in per DB policy file
diff --git a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/DatabaseRequiredInRole.java b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/DatabaseRequiredInRole.java
index 7a3c42a..630d81f 100644
--- a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/DatabaseRequiredInRole.java
+++ b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/DatabaseRequiredInRole.java
@@ -18,6 +18,7 @@
import javax.annotation.Nullable;
+import org.apache.access.core.AccessURI;
import org.apache.access.core.Authorizable;
import org.apache.access.core.Database;
import org.apache.shiro.config.ConfigurationException;
@@ -33,16 +34,34 @@
Iterable<Authorizable> authorizables = parseRole(role);
/*
* Each permission in a non-global file must have a database
- * object.
+ * object except for URIs.
+ *
+ * We allow URIs to be specified in the per DB policy file for
+ * ease of mangeability. URIs will contain to remain server scope
+ * objects.
*/
boolean foundDatabaseInAuthorizables = false;
+ boolean foundURIInAuthorizables = false;
+ boolean allowURIInAuthorizables = false;
+
+ if ("true".equalsIgnoreCase(
+ System.getProperty(SimplePolicyEngine.ACCESS_ALLOW_URI_PER_DB_POLICYFILE))) {
+ allowURIInAuthorizables = true;
+ }
+
for(Authorizable authorizable : authorizables) {
if(authorizable instanceof Database) {
foundDatabaseInAuthorizables = true;
- break;
+ }
+ if (authorizable instanceof AccessURI) {
+ if (foundDatabaseInAuthorizables) {
+ String msg = "URI object is specified at DB scope in " + role;
+ throw new ConfigurationException(msg);
+ }
+ foundURIInAuthorizables = true;
}
}
- if(!foundDatabaseInAuthorizables) {
+ if(!foundDatabaseInAuthorizables && !(foundURIInAuthorizables && allowURIInAuthorizables)) {
String msg = "Missing database object in " + role;
throw new ConfigurationException(msg);
}
diff --git a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/Roles.java b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/Roles.java
index 71ccdcd..e43cb6f 100644
--- a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/Roles.java
+++ b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/Roles.java
@@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.io.Resources;
public class Roles {
private static final Logger LOGGER = LoggerFactory
@@ -41,14 +42,27 @@
this.globalRoles = globalRoles;
this.perDatabaseRoles = perDatabaseRoles;
}
- public ImmutableSet<String> getRoles(@Nullable String database, String group) {
+ public ImmutableSet<String> getRoles(@Nullable String database, String group, Boolean isURI) {
ImmutableSet.Builder<String> resultBuilder = ImmutableSet.builder();
+ String allowURIPerDbFile =
+ System.getProperty(SimplePolicyEngine.ACCESS_ALLOW_URI_PER_DB_POLICYFILE);
+ Boolean consultPerDbRolesForURI = isURI && ("true".equalsIgnoreCase(allowURIPerDbFile));
+
if(database != null) {
ImmutableSetMultimap<String, String> dbPolicies = perDatabaseRoles.get(database);
if(dbPolicies != null && dbPolicies.containsKey(group)) {
resultBuilder.addAll(dbPolicies.get(group));
}
}
+ if (consultPerDbRolesForURI) {
+ for(String db:perDatabaseRoles.keySet()) {
+ ImmutableSetMultimap<String, String> dbPolicies = perDatabaseRoles.get(db);
+ if(dbPolicies != null && dbPolicies.containsKey(group)) {
+ resultBuilder.addAll(dbPolicies.get(group));
+ }
+ }
+ }
+
if(globalRoles.containsKey(group)) {
resultBuilder.addAll(globalRoles.get(group));
}
diff --git a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/SimplePolicyEngine.java b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/SimplePolicyEngine.java
index 8e68dc0..91f0953 100644
--- a/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/SimplePolicyEngine.java
+++ b/access-provider/access-provider-file/src/main/java/org/apache/access/provider/file/SimplePolicyEngine.java
@@ -33,6 +33,7 @@
import javax.annotation.Nullable;
+import org.apache.access.core.AccessURI;
import org.apache.access.core.Authorizable;
import org.apache.access.core.Database;
import org.apache.hadoop.conf.Configuration;
@@ -67,6 +68,7 @@
private final String serverName;
private final List<Path> perDbResources = Lists.newArrayList();
private final AtomicReference<Roles> rolesReference;
+ public final static String ACCESS_ALLOW_URI_PER_DB_POLICYFILE = "access.allow.uri.db.policyfile";
public SimplePolicyEngine(String resourcePath, String serverName) throws IOException {
this(new Configuration(), new Path(resourcePath), serverName);
@@ -243,17 +245,22 @@
public ImmutableSetMultimap<String, String> getPermissions(List<Authorizable> authorizables, List<String> groups) {
Roles roles = rolesReference.get();
String database = null;
+ Boolean isURI = false;
for(Authorizable authorizable : authorizables) {
if(authorizable instanceof Database) {
database = authorizable.getName();
}
+ if (authorizable instanceof AccessURI) {
+ isURI = true;
+ }
}
+
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Getting permissions for {} via {}", groups, database);
}
ImmutableSetMultimap.Builder<String, String> resultBuilder = ImmutableSetMultimap.builder();
for(String group : groups) {
- resultBuilder.putAll(group, roles.getRoles(database, group));
+ resultBuilder.putAll(group, roles.getRoles(database, group, isURI));
}
ImmutableSetMultimap<String, String> result = resultBuilder.build();
if(LOGGER.isDebugEnabled()) {
diff --git a/access-provider/access-provider-file/src/test/java/org/apache/access/provider/file/TestDatabaseRequiredInRole.java b/access-provider/access-provider-file/src/test/java/org/apache/access/provider/file/TestDatabaseRequiredInRole.java
new file mode 100644
index 0000000..8757c05
--- /dev/null
+++ b/access-provider/access-provider-file/src/test/java/org/apache/access/provider/file/TestDatabaseRequiredInRole.java
@@ -0,0 +1,48 @@
+/*
+ * 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.provider.file;
+
+import junit.framework.Assert;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.junit.Test;
+
+public class TestDatabaseRequiredInRole {
+
+ @Test
+ public void testURIInPerDbPolicyFile() throws Exception {
+ DatabaseRequiredInRole dbRequiredInRole = new DatabaseRequiredInRole();
+ System.setProperty("access.allow.uri.db.policyfile", "true");
+ dbRequiredInRole.validate("db1",
+ "server=server1->URI=file:///user/hive/warehouse/tab1");
+ System.setProperty("access.allow.uri.db.policyfile", "false");
+ }
+
+ @Test
+ public void testURIWithDBInPerDbPolicyFile() throws Exception {
+ DatabaseRequiredInRole dbRequiredInRole = new DatabaseRequiredInRole();
+ try {
+ dbRequiredInRole.validate("db1",
+ "server=server1->db=db1->URI=file:///user/hive/warehouse/tab1");
+ Assert.fail("Expected ConfigurationException");
+ } catch (ConfigurationException e) {
+ ;
+ }
+ }
+}
\ No newline at end of file
diff --git a/access-tests/src/test/java/org/apache/access/tests/e2e/TestPerDBConfiguration.java b/access-tests/src/test/java/org/apache/access/tests/e2e/TestPerDBConfiguration.java
index 442f7b3..32fc188 100644
--- a/access-tests/src/test/java/org/apache/access/tests/e2e/TestPerDBConfiguration.java
+++ b/access-tests/src/test/java/org/apache/access/tests/e2e/TestPerDBConfiguration.java
@@ -26,6 +26,7 @@
import java.sql.SQLException;
import java.sql.Statement;
+import org.apache.access.provider.file.SimplePolicyEngine;
import org.junit.After;
import org.junit.Test;
@@ -299,6 +300,114 @@
connection.close();
}
+ @Test
+ public void testPerDBPolicyFileWithURI() throws Exception {
+ context = createContext();
+ File policyFile = context.getPolicyFile();
+ File db2PolicyFile = new File(policyFile.getParent(), DB2_POLICY_FILE);
+ File dataDir = context.getDataDir();
+ //copy data file to test dir
+ File dataFile = new File(dataDir, MULTI_TYPE_DATA_FILE_NAME);
+ FileOutputStream to = new FileOutputStream(dataFile);
+ Resources.copy(Resources.getResource(MULTI_TYPE_DATA_FILE_NAME), to);
+ to.close();
+ //delete existing policy file; create new policy file
+ assertTrue("Could not delete " + policyFile, context.deletePolicyFile());
+ assertTrue("Could not delete " + db2PolicyFile,!db2PolicyFile.exists() || db2PolicyFile.delete());
+
+ String[] policyFileContents = {
+ // groups : role -> group
+ "[groups]",
+ "admin = all_server",
+ "user_group1 = select_tbl1",
+ "user_group2 = select_tbl2",
+ // roles: privileges -> role
+ "[roles]",
+ "all_server = server=server1",
+ "select_tbl1 = server=server1->db=db1->table=tbl1->action=select",
+ // users: users -> groups
+ "[users]",
+ "hive = admin",
+ "user_1 = user_group1",
+ "user_2 = user_group2",
+ "[databases]",
+ "db2 = " + db2PolicyFile.getPath(),
+ };
+ context.makeNewPolicy(policyFileContents);
+
+ String[] db2PolicyFileContents = {
+ "[groups]",
+ "user_group2 = select_tbl2, data_read, insert_tbl2",
+ "[roles]",
+ "select_tbl2 = server=server1->db=db2->table=tbl2->action=select",
+ "insert_tbl2 = server=server1->db=db2->table=tbl2->action=insert",
+ "data_read = server=server1->URI=file://" + dataFile
+ };
+ Files.write(Joiner.on("\n").join(db2PolicyFileContents), db2PolicyFile, Charsets.UTF_8);
+ // ugly hack: needs to go away once this becomes a config property. Note that this property
+ // will not be set with external HS and this test will fail. Hope is this fix will go away
+ // by then.
+ System.setProperty(SimplePolicyEngine.ACCESS_ALLOW_URI_PER_DB_POLICYFILE, "true");
+ // setup db objects needed by the test
+ Connection connection = context.createConnection("hive", "hive");
+ Statement statement = context.createStatement(connection);
+
+ statement.execute("DROP DATABASE IF EXISTS db1 CASCADE");
+ statement.execute("DROP DATABASE IF EXISTS db2 CASCADE");
+ statement.execute("CREATE DATABASE db1");
+ statement.execute("USE db1");
+ statement.execute("CREATE TABLE tbl1(B INT, A STRING) " +
+ " row format delimited fields terminated by '|' stored as textfile");
+ statement.execute("LOAD DATA LOCAL INPATH '" + dataFile.getPath() + "' INTO TABLE tbl1");
+ statement.execute("DROP DATABASE IF EXISTS db2 CASCADE");
+ statement.execute("CREATE DATABASE db2");
+ statement.execute("USE db2");
+ statement.execute("CREATE TABLE tbl2(B INT, A STRING) " +
+ " row format delimited fields terminated by '|' stored as textfile");
+ statement.execute("LOAD DATA LOCAL INPATH '" + dataFile.getPath() + "' INTO TABLE tbl2");
+ statement.close();
+ connection.close();
+
+ // test execution
+ connection = context.createConnection("user_1", "password");
+ statement = context.createStatement(connection);
+ statement.execute("USE db1");
+ // test user1 can execute query on tbl1
+ verifyCount(statement, "SELECT COUNT(*) FROM tbl1");
+
+ // user1 cannot query db2.tbl2
+ context.assertAuthzException(statement, "USE db2");
+ context.assertAuthzException(statement, "SELECT COUNT(*) FROM db2.tbl2");
+ statement.close();
+ connection.close();
+
+ // test per-db file for db2
+ connection = context.createConnection("user_2", "password");
+ statement = context.createStatement(connection);
+ statement.execute("USE db2");
+ // test user2 can execute query on tbl2
+ verifyCount(statement, "SELECT COUNT(*) FROM tbl2");
+
+ // verify user2 can execute LOAD
+ statement.execute("LOAD DATA LOCAL INPATH '" + dataFile.getPath() + "' INTO TABLE tbl2");
+
+ // user2 cannot query db1.tbl1
+ context.assertAuthzException(statement, "SELECT COUNT(*) FROM db1.tbl1");
+ context.assertAuthzException(statement, "USE db1");
+
+ statement.close();
+ connection.close();
+
+ //test cleanup
+ connection = context.createConnection("hive", "hive");
+ statement = context.createStatement(connection);
+ statement.execute("DROP DATABASE db1 CASCADE");
+ statement.execute("DROP DATABASE db2 CASCADE");
+ statement.close();
+ connection.close();
+ System.setProperty(SimplePolicyEngine.ACCESS_ALLOW_URI_PER_DB_POLICYFILE, "false");
+ }
+
private void verifyCount(Statement statement, String query) throws SQLException {
ResultSet resultSet = statement.executeQuery(query);
int count = 0;