KYLIN-2761 Table Level ACL, refactor code and add unit tests.

minor, fix ut


Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/06138f51
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/06138f51
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/06138f51

Branch: refs/heads/master
Commit: 06138f51b19a9b23b2fbb1665e5bae5f31e8f2b1
Parents: a30c8d2
Author: tttMelody <245915...@qq.com>
Authored: Thu Sep 14 11:37:08 2017 +0800
Committer: Hongbin Ma <m...@kyligence.io>
Committed: Fri Sep 15 11:24:54 2017 +0800

----------------------------------------------------------------------
 .../org/apache/kylin/metadata/acl/TableACL.java | 63 ++++++++-----
 .../kylin/metadata/acl/TableACLManager.java     | 67 ++++++++++++--
 .../kylin/metadata/project/ProjectManager.java  |  9 ++
 .../apache/kylin/metadata/acl/TableACLTest.java | 55 +++++++++--
 .../relnode/OLAPToEnumerableConverter.java      | 14 +--
 .../kylin/query/security/QuerACLTestUtil.java   | 43 +++++++++
 .../kylin/query/security/QueryIntercept.java    | 35 ++++++-
 query/src/test/resources/log4j.properties       | 26 ++++++
 .../broadcaster/BroadcasterReceiveServlet.java  | 77 ++++++++++++++++
 .../kylin/rest/controller/AccessController.java | 21 ++++-
 .../kylin/rest/security/TableIntercept.java     | 42 +++------
 .../kylin/rest/service/TableACLService.java     | 23 +++--
 .../apache/kylin/rest/util/ValidateUtil.java    | 41 +++++++--
 .../broadcaster/BroadcasterReceiveServlet.java  | 77 ----------------
 .../rest/controller/AccessControllerTest.java   |  9 +-
 .../rest/security/QueryWithTableACLTest.java    | 72 +++++++++++++++
 .../rest/security/TableACLManagerTest.java      | 44 +++++++++
 .../kylin/rest/service/TableACLServiceTest.java | 22 +++--
 .../rest/util/MultiNodeManagerTestBase.java     | 97 ++++++++++++++++++++
 19 files changed, 655 insertions(+), 182 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java 
b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java
index 6d6f158..89ef108 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java
@@ -22,6 +22,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.kylin.common.persistence.RootPersistentEntity;
 
@@ -39,7 +41,7 @@ public class TableACL extends RootPersistentEntity {
     @JsonProperty()
     private Map<String, TableBlackList> userTableBlackList;
 
-    public TableACL() {
+    TableACL() {
         userTableBlackList = new HashMap<>();
     }
 
@@ -47,8 +49,9 @@ public class TableACL extends RootPersistentEntity {
         return userTableBlackList;
     }
 
-    public List<String> getTableBlackList(String username) {
+    public Set<String> getTableBlackList(String username) {
         TableBlackList tableBlackList = userTableBlackList.get(username);
+        //table intercept will use this, return an empty set then null
         if (tableBlackList == null) {
             tableBlackList = new TableBlackList();
         }
@@ -56,7 +59,7 @@ public class TableACL extends RootPersistentEntity {
     }
 
     //get users that can not query the table
-    public List<String> getBlockedUserByTable(String table) {
+    public List<String> getUsersCannotQueryTheTbl(String table) {
         List<String> results = new ArrayList<>();
         for (String user : userTableBlackList.keySet()) {
             TableBlackList tables = userTableBlackList.get(user);
@@ -80,10 +83,33 @@ public class TableACL extends RootPersistentEntity {
 
         //before add, check exists
         checkACLExists(username, table, tableBlackList);
-        tableBlackList.add(table);
+        tableBlackList.addTbl(table);
         return this;
     }
 
+    public TableACL delete(String username, String table) {
+        checkTableInBlackList(username, table);
+        TableBlackList tableBlackList = userTableBlackList.get(username);
+        tableBlackList.removeTbl(table);
+        if (tableBlackList.isEmpty()) {
+            userTableBlackList.remove(username);
+        }
+        return this;
+    }
+
+    public TableACL delete(String username) {
+        checkUserHasACL(username);
+        userTableBlackList.remove(username);
+        return this;
+    }
+
+    private void checkUserHasACL(String username) {
+        if (userTableBlackList.get(username) == null || 
userTableBlackList.get(username).isEmpty()) {
+            throw new RuntimeException("Operation fail, can not grant user 
table query permission.User:" + username
+                    + " already has permission!");
+        }
+    }
+
     private void checkACLExists(String username, String table, TableBlackList 
tableBlackList) {
         if (tableBlackList.contains(table)) {
             throw new RuntimeException("Operation fail, can not revoke user's 
table query permission.Table ACL " + table
@@ -91,20 +117,13 @@ public class TableACL extends RootPersistentEntity {
         }
     }
 
-    public TableACL delete(String username, String table) {
-        if (isTableInBlackList(username, table)) {
+    private void checkTableInBlackList(String username, String table) {
+        if (userTableBlackList == null
+                || userTableBlackList.get(username) == null
+                || (!userTableBlackList.get(username).contains(table))) {
             throw new RuntimeException("Operation fail, can not grant user 
table query permission.Table ACL " + table
                     + ":" + username + " is not found!");
         }
-        TableBlackList tableBlackList = userTableBlackList.get(username);
-        tableBlackList.remove(table);
-        return this;
-    }
-
-    private boolean isTableInBlackList(String username, String table) {
-        return userTableBlackList == null
-                || userTableBlackList.get(username) == null
-                || (!userTableBlackList.get(username).contains(table));
     }
 
     @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
@@ -113,10 +132,10 @@ public class TableACL extends RootPersistentEntity {
             setterVisibility = JsonAutoDetect.Visibility.NONE)
     static class TableBlackList {
         @JsonProperty()
-        List<String> tables;
+        Set<String> tables;
 
         TableBlackList() {
-            tables = new ArrayList<>();
+            tables = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
         }
 
         public int size() {
@@ -131,15 +150,15 @@ public class TableACL extends RootPersistentEntity {
             return tables.contains(s);
         }
 
-        public boolean add(String s) {
-            return tables.add(s);
+        void addTbl(String s) {
+            tables.add(s);
         }
 
-        public boolean remove(String s) {
-            return tables.remove(s);
+        void removeTbl(String s) {
+            tables.remove(s);
         }
 
-        public List<String> getTables() {
+        public Set<String> getTables() {
             return tables;
         }
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
 
b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
index 53c0843..7bea743 100644
--- 
a/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
@@ -19,6 +19,7 @@
 package org.apache.kylin.metadata.acl;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -26,6 +27,8 @@ import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.persistence.JsonSerializer;
 import org.apache.kylin.common.persistence.ResourceStore;
 import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.metadata.cachesync.Broadcaster;
+import org.apache.kylin.metadata.cachesync.CaseInsensitiveStringCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,7 +38,7 @@ public class TableACLManager {
 
     private static final Logger logger = 
LoggerFactory.getLogger(TableACLManager.class);
 
-    public static final Serializer<TableACL> TABLE_ACL_SERIALIZER = new 
JsonSerializer<>(TableACL.class);
+    private static final Serializer<TableACL> TABLE_ACL_SERIALIZER = new 
JsonSerializer<>(TableACL.class);
     private static final String DIR_PREFIX = "/table_acl/";
 
     // static cached instances
@@ -77,9 +80,27 @@ public class TableACLManager {
     // 
============================================================================
 
     private KylinConfig config;
+    // user ==> TableACL
+    private CaseInsensitiveStringCache<TableACL> tableACLMap;
 
-    private TableACLManager(KylinConfig config) throws IOException {
+    public TableACLManager(KylinConfig config) throws IOException {
+        logger.info("Initializing TableACLManager with config " + config);
         this.config = config;
+        this.tableACLMap = new CaseInsensitiveStringCache<>(config, 
"table_acl");
+        loadAllTableACL();
+        Broadcaster.getInstance(config).registerListener(new 
TableACLSyncListener(), "table_acl");
+    }
+
+    private class TableACLSyncListener extends Broadcaster.Listener {
+        @Override
+        public void onClearAll(Broadcaster broadcaster) throws IOException {
+            clearCache();
+        }
+
+        @Override
+        public void onEntityChange(Broadcaster broadcaster, String entity, 
Broadcaster.Event event, String cacheKey) throws IOException {
+            reloadTableACL(cacheKey);
+        }
     }
 
     public KylinConfig getConfig() {
@@ -90,7 +111,31 @@ public class TableACLManager {
         return ResourceStore.getStore(this.config);
     }
 
-    public TableACL getTableACL(String project) throws IOException {
+    public TableACL getTableACLByCache(String project){
+        TableACL tableACL = tableACLMap.get(project);
+        if (tableACL == null) {
+            return new TableACL();
+        }
+        return tableACL;
+    }
+
+    private void loadAllTableACL() throws IOException {
+        ResourceStore store = getStore();
+        List<String> paths = store.collectResourceRecursively("/table_acl", 
"");
+        final int prefixLen = DIR_PREFIX.length();
+        for (String path : paths) {
+            String project = path.substring(prefixLen, path.length());
+            reloadTableACL(project);
+        }
+        logger.info("Loading table ACL from folder " + 
store.getReadableResourcePath("/table_acl"));
+    }
+
+    private void reloadTableACL(String project) throws IOException {
+        TableACL tableACLRecord = getTableACL(project);
+        tableACLMap.putLocal(project, tableACLRecord);
+    }
+
+    private TableACL getTableACL(String project) throws IOException {
         String path = DIR_PREFIX + project;
         TableACL tableACLRecord = getStore().getResource(path, TableACL.class, 
TABLE_ACL_SERIALIZER);
         if (tableACLRecord == null || tableACLRecord.getUserTableBlackList() 
== null) {
@@ -101,14 +146,22 @@ public class TableACLManager {
 
     public void addTableACL(String project, String username, String table) 
throws IOException {
         String path = DIR_PREFIX + project;
-        TableACL tableACL = getTableACL(project);
-        getStore().putResource(path, tableACL.add(username, table), 
System.currentTimeMillis(), TABLE_ACL_SERIALIZER);
+        TableACL tableACL = getTableACL(project).add(username, table);
+        getStore().putResource(path, tableACL, System.currentTimeMillis(), 
TABLE_ACL_SERIALIZER);
+        tableACLMap.put(project, tableACL);
     }
 
     public void deleteTableACL(String project, String username, String table) 
throws IOException {
         String path = DIR_PREFIX + project;
-        TableACL tableACL = getTableACL(project);
-        getStore().putResource(path, tableACL.delete(username, table), 
System.currentTimeMillis(), TABLE_ACL_SERIALIZER);
+        TableACL tableACL = getTableACL(project).delete(username, table);
+        getStore().putResource(path, tableACL, System.currentTimeMillis(), 
TABLE_ACL_SERIALIZER);
+        tableACLMap.put(project, tableACL);
     }
 
+    public void deleteTableACL(String project, String username) throws 
IOException {
+        String path = DIR_PREFIX + project;
+        TableACL tableACL = getTableACL(project).delete(username);
+        getStore().putResource(path, tableACL, System.currentTimeMillis(), 
TABLE_ACL_SERIALIZER);
+        tableACLMap.put(project, tableACL);
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
 
b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
index bbbca4f..040871a 100644
--- 
a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
@@ -172,6 +172,15 @@ public class ProjectManager {
         return projectMap.get(projectName);
     }
 
+    public ProjectInstance getPrjByUuid(String uuid) {
+        Collection<ProjectInstance> copy = new 
ArrayList<ProjectInstance>(projectMap.values());
+        for (ProjectInstance prj : copy) {
+            if (uuid.equals(prj.getUuid()))
+                return prj;
+        }
+        return null;
+    }
+
     public ProjectInstance createProject(String projectName, String owner, 
String description,
             LinkedHashMap<String, String> overrideProps) throws IOException {
         logger.info("Creating project " + projectName);

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java 
b/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java
index cfcfbf4..4ad934d 100644
--- 
a/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java
+++ 
b/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java
@@ -21,35 +21,76 @@ package org.apache.kylin.metadata.acl;
 import org.junit.Assert;
 import org.junit.Test;
 
+import com.google.common.collect.Sets;
+
 public class TableACLTest {
 
     @Test
+    public void testCaseInsensitive() {
+        TableACL tableACL = new TableACL();
+        tableACL.add("u1", "t1");
+        try {
+            tableACL.add("u1", "T1");
+            Assert.fail("expecting some AlreadyExistsException here");
+        } catch (Exception e) {
+            Assert.assertEquals(
+                    "Operation fail, can not revoke user's table query 
permission.Table ACL T1:u1 already exists!",
+                    e.getMessage());
+        }
+        Assert.assertEquals(1, tableACL.getTableBlackList("u1").size());
+        Assert.assertTrue(tableACL.getTableBlackList("u1").contains("T1"));
+        tableACL.delete("u1", "T1");
+        Assert.assertEquals(0, tableACL.getTableBlackList("u1").size());
+    }
+
+    @Test
+    public void testDeleteToEmpty() {
+        TableACL tableACL = new TableACL();
+        tableACL.add("u1", "t1");
+        tableACL.delete("u1", "t1");
+        Assert.assertNotNull(tableACL.getTableBlackList("u1"));
+        Assert.assertTrue(tableACL.getTableBlackList("u1").isEmpty());
+        tableACL.add("u1", "t2");
+        Assert.assertEquals(1, tableACL.getTableBlackList("u1").size());
+    }
+
+    @Test
     public void testTableACL() {
         TableACL empty = new TableACL();
         try {
-            empty.delete("a", "b");
+            empty.delete("a", "DB.TABLE1");
+            Assert.fail("expecting some AlreadyExistsException here");
         } catch (Exception e) {
-            Assert.assertEquals(
-                    "Operation fail, can not grant user table query 
permission.Table ACL b:a is not found!",
+            Assert.assertEquals("Operation fail, can not grant user table 
query permission.Table ACL DB.TABLE1:a is not found!",
                     e.getMessage());
         }
 
         //add
         TableACL tableACL = new TableACL();
-        tableACL.add("user1", "table1");
+        tableACL.add("user1", "DB.TABLE1");
         Assert.assertEquals(1, tableACL.getUserTableBlackList().size());
 
         //add duplicated
         try {
-            tableACL.add("user1", "table1");
+            tableACL.add("user1", "DB.TABLE1");
+            Assert.fail("expecting some AlreadyExistsException here");
         } catch (Exception e) {
             Assert.assertEquals(
-                    "Operation fail, can not revoke user's table query 
permission.Table ACL table1:user1 already exists!",
+                    "Operation fail, can not revoke user's table query 
permission.Table ACL DB.TABLE1:user1 already exists!",
                     e.getMessage());
         }
 
         //add
-        tableACL.add("user2", "table1");
+        tableACL.add("user2", "DB.TABLE1");
         Assert.assertEquals(2, tableACL.getUserTableBlackList().size());
+
+        //delete
+        Assert.assertEquals(Sets.newHashSet("DB.TABLE1"), 
tableACL.getUserTableBlackList().get("user2").getTables());
+        tableACL.delete("user2", "DB.TABLE1");
+        Assert.assertNull(tableACL.getUserTableBlackList().get("user2"));
+
+        //delete user's all table
+        tableACL.delete("user1");
+        Assert.assertNull(tableACL.getUserTableBlackList().get("user1"));
     }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
 
b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
index db934cf..637f863 100644
--- 
a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
+++ 
b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
@@ -74,12 +74,10 @@ public class OLAPToEnumerableConverter extends 
ConverterImpl implements Enumerab
         // identify model & realization
         List<OLAPContext> contexts = listContextsHavingScan();
 
-        String project = getProject(contexts);
-        String user = getUser(contexts);
-
+        // intercept query
         List<QueryIntercept> intercepts = 
QueryInterceptUtil.getQueryIntercepts();
         for (QueryIntercept intercept : intercepts) {
-            intercept.intercept(project, user, contexts);
+            intercept.intercept(contexts);
         }
 
         RealizationChooser.selectRealization(contexts);
@@ -126,12 +124,4 @@ public class OLAPToEnumerableConverter extends 
ConverterImpl implements Enumerab
             accessController.check(contexts, config);
         }
     }
-
-    public String getProject(List<OLAPContext> contexts) {
-        return contexts.get(0).olapSchema.getProjectName();
-    }
-
-    public String getUser(List<OLAPContext> contexts) {
-        return contexts.get(0).olapAuthen.getUsername();
-    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/query/src/main/java/org/apache/kylin/query/security/QuerACLTestUtil.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/security/QuerACLTestUtil.java 
b/query/src/main/java/org/apache/kylin/query/security/QuerACLTestUtil.java
new file mode 100644
index 0000000..123ae5b
--- /dev/null
+++ b/query/src/main/java/org/apache/kylin/query/security/QuerACLTestUtil.java
@@ -0,0 +1,43 @@
+/*
+ * 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.kylin.query.security;
+
+import org.apache.kylin.query.QueryConnection;
+import org.apache.kylin.query.relnode.OLAPContext;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+public class QuerACLTestUtil {
+    public static void setUser(String username) {
+        Map<String, String> auth = new HashMap<>();
+        auth.put(OLAPContext.PRM_USER_AUTHEN_INFO, username);
+        OLAPContext.setParameters(auth);
+    }
+
+    public static ResultSet mockQuery(String project, String sql) throws 
SQLException {
+        Connection conn = QueryConnection.getConnection(project);
+        Statement statement = conn.createStatement();
+        return statement.executeQuery(sql);
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java 
b/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java
index 493cc25..8df0da7 100644
--- a/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java
+++ b/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java
@@ -23,8 +23,37 @@ import java.util.List;
 
 import org.apache.kylin.query.relnode.OLAPContext;
 
-public interface QueryIntercept {
-    void intercept(String project, String username, List<OLAPContext> 
contexts);
+public abstract class QueryIntercept {
 
-    Collection<String> getQueryIdentifiers(List<OLAPContext> contexts);
+    public void intercept(List<OLAPContext> contexts) {
+        Collection<String> userIdentifierBlackList = 
getIdentifierBlackList(contexts);
+        intercept(contexts, userIdentifierBlackList);
+    }
+
+    private void intercept(List<OLAPContext> contexts, Collection<String> 
blackList) {
+        if (blackList.isEmpty()) {
+            return;
+        }
+
+        Collection<String> queryCols = getQueryIdentifiers(contexts);
+        for (String id : blackList) {
+            if (queryCols.contains(id.toUpperCase())) {
+                throw new AccessDeniedException(getIdentifierType() + ":" + 
id);
+            }
+        }
+    }
+
+    protected abstract Collection<String> 
getQueryIdentifiers(List<OLAPContext> contexts);
+
+    protected abstract Collection<String> 
getIdentifierBlackList(List<OLAPContext> contexts);
+
+    protected abstract String getIdentifierType();
+
+    protected String getProject(List<OLAPContext> contexts) {
+        return contexts.get(0).olapSchema.getProjectName();
+    }
+
+    protected String getUser(List<OLAPContext> contexts) {
+        return contexts.get(0).olapAuthen.getUsername();
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/query/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/query/src/test/resources/log4j.properties 
b/query/src/test/resources/log4j.properties
new file mode 100644
index 0000000..533544b
--- /dev/null
+++ b/query/src/test/resources/log4j.properties
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+log4j.rootLogger=INFO,stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+#Don't add line number (%L) as it's too costly!
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} : 
%m%n
+#log4j.logger.org.apache.hadoop=ERROR
+log4j.logger.org.apache.kylin=DEBUG
+log4j.logger.io.kyligence=DEBUG
+log4j.logger.org.springframework=WARN

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server-base/src/main/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
 
b/server-base/src/main/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
new file mode 100644
index 0000000..0a9c0bf
--- /dev/null
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
@@ -0,0 +1,77 @@
+/*
+ * 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.kylin.rest.broadcaster;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ */
+public class BroadcasterReceiveServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    public interface BroadcasterHandler {
+
+        void handle(String type, String name, String event);
+    }
+
+    private BroadcasterHandler handler;
+
+    public BroadcasterReceiveServlet(BroadcasterHandler handler) {
+        this.handler = handler;
+    }
+
+    private static final Pattern PATTERN = Pattern.compile("/(.+)/(.+)/(.+)");
+
+    @Override
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+        handle(req, resp);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+        handle(req, resp);
+    }
+
+    private void handle(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+        final String startString = "/kylin/api/cache";
+        final String requestURI = req.getRequestURI();
+        final String substring = 
requestURI.substring(requestURI.indexOf(startString) + startString.length());
+        final Matcher matcher = PATTERN.matcher(substring);
+        if (matcher.matches()) {
+            String type = matcher.group(1);
+            String name = matcher.group(2);
+            String event = matcher.group(3);
+            if (handler != null) {
+                handler.handle(type, name, event);
+            }
+            resp.getWriter().write("type:" + type + " name:" + name + " 
event:" + event);
+        } else {
+            resp.getWriter().write("not valid uri");
+        }
+        resp.getWriter().close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java
 
b/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java
index a88c342..014df68 100644
--- 
a/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java
@@ -24,8 +24,11 @@ import java.util.List;
 import org.apache.kylin.common.persistence.AclEntity;
 import org.apache.kylin.rest.request.AccessRequest;
 import org.apache.kylin.rest.response.AccessEntryResponse;
+import org.apache.kylin.rest.security.AclEntityType;
 import org.apache.kylin.rest.security.AclPermissionFactory;
 import org.apache.kylin.rest.service.AccessService;
+import org.apache.kylin.rest.service.ProjectService;
+import org.apache.kylin.rest.service.TableACLService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.acls.model.Acl;
@@ -50,6 +53,14 @@ public class AccessController extends BasicController {
     @Qualifier("accessService")
     private AccessService accessService;
 
+    @Autowired
+    @Qualifier("projectService")
+    private ProjectService projectService;
+
+    @Autowired
+    @Qualifier("TableAclService")
+    private TableACLService tableACLService;
+
     /**
      * Get access entry list of a domain object
      * 
@@ -103,10 +114,16 @@ public class AccessController extends BasicController {
      * @param accessRequest
      */
     @RequestMapping(value = "/{type}/{uuid}", method = { RequestMethod.DELETE 
}, produces = { "application/json" })
-    public List<AccessEntryResponse> revoke(@PathVariable String type, 
@PathVariable String uuid, AccessRequest accessRequest) {
+    public List<AccessEntryResponse> revoke(@PathVariable String type, 
@PathVariable String uuid, AccessRequest accessRequest) throws IOException {
         AclEntity ae = accessService.getAclEntity(type, uuid);
         Acl acl = accessService.revoke(ae, accessRequest.getAccessEntryId());
-
+        if (AclEntityType.PROJECT_INSTANCE.equals(type)) {
+            String prj = 
projectService.getProjectManager().getPrjByUuid(uuid).getName();
+            String username = accessRequest.getSid();
+            if (tableACLService.exists(prj, username)) {
+                tableACLService.deleteFromTableBlackList(prj, username);
+            }
+        }
         return accessService.generateAceResponses(acl);
     }
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java 
b/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java
index a980658..c87f674 100644
--- 
a/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java
@@ -18,49 +18,35 @@
 
 package org.apache.kylin.rest.security;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.metadata.acl.TableACLManager;
 import org.apache.kylin.query.relnode.OLAPContext;
-import org.apache.kylin.query.security.AccessDeniedException;
 import org.apache.kylin.query.security.QueryIntercept;
 import org.apache.kylin.query.security.QueryInterceptUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-public class TableIntercept implements QueryIntercept{
-    private static final Logger logger = 
LoggerFactory.getLogger(QueryIntercept.class);
+public class TableIntercept extends QueryIntercept{
 
     @Override
-    public void intercept(String project, String username, List<OLAPContext> 
contexts) {
-        List<String> userTableBlackList = getTableBlackList(project, username);
-        if (userTableBlackList.isEmpty()) {
-            return;
-        }
-        Set<String> queryTbls = getQueryIdentifiers(contexts);
-        for (String tbl : userTableBlackList) {
-            if (queryTbls.contains(tbl.toUpperCase())) {
-                throw new AccessDeniedException("table:" + tbl);
-            }
-        }
+    public Set<String> getQueryIdentifiers(List<OLAPContext> contexts) {
+        return QueryInterceptUtil.getAllTblsWithSchema(contexts);
     }
 
     @Override
-    public Set<String> getQueryIdentifiers(List<OLAPContext> contexts) {
-        return QueryInterceptUtil.getAllTblsWithSchema(contexts);
+    protected Set<String> getIdentifierBlackList(List<OLAPContext> contexts) {
+        String project = getProject(contexts);
+        String username = getUser(contexts);
+
+        return TableACLManager
+                .getInstance(KylinConfig.getInstanceFromEnv())
+                .getTableACLByCache(project)
+                .getTableBlackList(username);
     }
 
-    private List<String> getTableBlackList(String project, String username) {
-        List<String> tableBlackList = new ArrayList<>();
-        try {
-            tableBlackList = 
TableACLManager.getInstance(KylinConfig.getInstanceFromEnv()).getTableACL(project).getTableBlackList(username);
-        } catch (IOException e) {
-            logger.error("get table black list fail. " + e.getMessage());
-        }
-        return tableBlackList;
+    @Override
+    protected String getIdentifierType() {
+        return "table";
     }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java 
b/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java
index 3f1b515..702d0c3 100644
--- 
a/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java
@@ -21,6 +21,7 @@ package org.apache.kylin.rest.service;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.kylin.metadata.acl.TableACL;
 import org.apache.kylin.rest.util.AclEvaluate;
@@ -37,9 +38,9 @@ public class TableACLService extends BasicService {
     private AclEvaluate aclEvaluate;
 
     // cuz in the frontend shows user can visit the table, but in the backend 
stored user that can not visit the table
-    public List<String> getTableWhiteListByTable(String project, String table, 
List<String> allUsers)
+    public List<String> getUsersCanQueryTheTbl(String project, String table, 
Set<String> allUsers)
             throws IOException {
-        List<String> blockedUsers = getBlockedUserByTable(project, table);
+        List<String> blockedUsers = getUsersCannotQueryTheTbl(project, table);
         List<String> whiteUsers = new ArrayList<>();
         for (String u : allUsers) {
             if (!blockedUsers.contains(u)) {
@@ -49,13 +50,18 @@ public class TableACLService extends BasicService {
         return whiteUsers;
     }
 
-    public TableACL getTableACLByProject(String project) throws IOException {
-        return getTableACLManager().getTableACL(project);
+    TableACL getTableACLByProject(String project) throws IOException {
+        return getTableACLManager().getTableACLByCache(project);
     }
 
-    public List<String> getBlockedUserByTable(String project, String table) 
throws IOException {
+    public boolean exists(String project, String username) throws IOException {
         aclEvaluate.checkProjectWritePermission(project);
-        return getTableACLByProject(project).getBlockedUserByTable(table);
+        return 
getTableACLByProject(project).getUserTableBlackList().containsKey(username);
+    }
+
+    public List<String> getUsersCannotQueryTheTbl(String project, String 
table) throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
+        return getTableACLByProject(project).getUsersCannotQueryTheTbl(table);
     }
 
     public void addToTableBlackList(String project, String username, String 
table) throws IOException {
@@ -67,4 +73,9 @@ public class TableACLService extends BasicService {
         aclEvaluate.checkProjectAdminPermission(project);
         getTableACLManager().deleteTableACL(project, username, table);
     }
+
+    public void deleteFromTableBlackList(String project, String username) 
throws IOException {
+        aclEvaluate.checkProjectAdminPermission(project);
+        getTableACLManager().deleteTableACL(project, username);
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java 
b/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java
index a95bdd4..fc106ce 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java
@@ -21,16 +21,27 @@ package org.apache.kylin.rest.util;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.persistence.AclEntity;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.TableDesc;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.security.ManagedUser;
+import org.apache.kylin.rest.service.AccessService;
+import org.apache.kylin.rest.service.ProjectService;
 import org.apache.kylin.rest.service.TableService;
 import org.apache.kylin.rest.service.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.acls.domain.PrincipalSid;
+import org.springframework.security.acls.model.AccessControlEntry;
+import org.springframework.security.acls.model.Acl;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.stereotype.Component;
 
 import com.google.common.base.Preconditions;
@@ -46,6 +57,14 @@ public class ValidateUtil {
     @Qualifier("tableService")
     private TableService tableService;
 
+    @Autowired
+    @Qualifier("projectService")
+    private ProjectService projectService;
+
+    @Autowired
+    @Qualifier("accessService")
+    private AccessService accessService;
+
     public void validateUser(String username) {
         if (!userService.userExists(username)) {
             throw new RuntimeException("Operation failed, user:" + username + 
" not exists");
@@ -90,16 +109,26 @@ public class ValidateUtil {
         return cols;
     }
 
-    public List<String > getAllUsers() throws IOException {
-        List<ManagedUser> managedUsers = userService.listUsers();
-        List<String> allUsers = new ArrayList<>();
-        for (ManagedUser managedUser : managedUsers) {
-            allUsers.add(managedUser.getUsername());
+    public Set<String> getAllUsers(String project) throws IOException {
+        Set<String> allUsers = new HashSet<>();
+        // add users that is global admin
+        for (ManagedUser managedUser : userService.listUsers()) {
+            if (managedUser.getAuthorities().contains(new 
SimpleGrantedAuthority(Constant.ROLE_ADMIN))) {
+                allUsers.add(managedUser.getUsername());
+            }
+        }
+
+        // add users that has project permission
+        ProjectInstance prj = 
projectService.getProjectManager().getProject(project);
+        AclEntity ae = accessService.getAclEntity("ProjectInstance", 
prj.getUuid());
+        Acl acl = accessService.getAcl(ae);
+        for (AccessControlEntry ace : acl.getEntries()) {
+            allUsers.add(((PrincipalSid) ace.getSid()).getPrincipal());
         }
         return allUsers;
     }
 
-    public void vaildateArgs(String... args) {
+    public void validateArgs(String... args) {
         for (String arg : args) {
             Preconditions.checkState(!StringUtils.isEmpty(arg));
         }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
 
b/server/src/test/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
deleted file mode 100644
index 0a9c0bf..0000000
--- 
a/server/src/test/java/org/apache/kylin/rest/broadcaster/BroadcasterReceiveServlet.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.kylin.rest.broadcaster;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- */
-public class BroadcasterReceiveServlet extends HttpServlet {
-
-    private static final long serialVersionUID = 1L;
-
-    public interface BroadcasterHandler {
-
-        void handle(String type, String name, String event);
-    }
-
-    private BroadcasterHandler handler;
-
-    public BroadcasterReceiveServlet(BroadcasterHandler handler) {
-        this.handler = handler;
-    }
-
-    private static final Pattern PATTERN = Pattern.compile("/(.+)/(.+)/(.+)");
-
-    @Override
-    protected void doPut(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
-        handle(req, resp);
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
-        handle(req, resp);
-    }
-
-    private void handle(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
-        final String startString = "/kylin/api/cache";
-        final String requestURI = req.getRequestURI();
-        final String substring = 
requestURI.substring(requestURI.indexOf(startString) + startString.length());
-        final Matcher matcher = PATTERN.matcher(substring);
-        if (matcher.matches()) {
-            String type = matcher.group(1);
-            String name = matcher.group(2);
-            String event = matcher.group(3);
-            if (handler != null) {
-                handler.handle(type, name, event);
-            }
-            resp.getWriter().write("type:" + type + " name:" + name + " 
event:" + event);
-        } else {
-            resp.getWriter().write("not valid uri");
-        }
-        resp.getWriter().close();
-    }
-}

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
 
b/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
index 059dfdb..1040a84 100644
--- 
a/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
+++ 
b/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
@@ -50,8 +50,6 @@ import static org.junit.Assert.assertTrue;
  */
 public class AccessControllerTest extends ServiceTestBase implements 
AclEntityType, AclPermissionType {
 
-    private AccessController accessController;
-
     private CubeController cubeController;
 
     private ProjectController projectController;
@@ -67,6 +65,10 @@ public class AccessControllerTest extends ServiceTestBase 
implements AclEntityTy
     ProjectService projectService;
 
     @Autowired
+    @Qualifier("accessController")
+    AccessController accessController;
+
+    @Autowired
     @Qualifier("cubeMgmtService")
     CubeService cubeService;
 
@@ -77,8 +79,6 @@ public class AccessControllerTest extends ServiceTestBase 
implements AclEntityTy
     @Before
     public void setup() throws Exception {
         super.setup();
-        accessController = new AccessController();
-        accessController.setAccessService(accessService);
         cubeController = new CubeController();
         cubeController.setCubeService(cubeService);
         projectController = new ProjectController();
@@ -115,6 +115,7 @@ public class AccessControllerTest extends ServiceTestBase 
implements AclEntityTy
         accessRequest = new AccessRequest();
         accessRequest.setAccessEntryId(aeId);
         accessRequest.setPermission(READ);
+        accessRequest.setSid("ADMIN");
         aes = accessController.revoke(CUBE_INSTANCE, 
"a24ca905-1fc6-4f67-985c-38fa5aeafd92", accessRequest);
         assertEquals(0, aes.size());
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/security/QueryWithTableACLTest.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/security/QueryWithTableACLTest.java
 
b/server/src/test/java/org/apache/kylin/rest/security/QueryWithTableACLTest.java
new file mode 100644
index 0000000..1f3539e
--- /dev/null
+++ 
b/server/src/test/java/org/apache/kylin/rest/security/QueryWithTableACLTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.kylin.rest.security;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
+import org.apache.kylin.metadata.acl.TableACLManager;
+import org.apache.kylin.query.security.AccessDeniedException;
+import org.apache.kylin.query.security.QuerACLTestUtil;
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+public class QueryWithTableACLTest extends LocalFileMetadataTestCase {
+    private static final String PROJECT = "DEFAULT";
+    private static final String ADMIN = "ADMIN";
+    private static final String MODELER = "MODELER";
+    private static final String STREAMING_TABLE = "DEFAULT.STREAMING_TABLE";
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        this.createTestMetadata();
+    }
+
+    @Test
+    public void testNormalQuery() throws SQLException {
+        QuerACLTestUtil.setUser(ADMIN);
+        QuerACLTestUtil.mockQuery(PROJECT, "select * from STREAMING_TABLE");
+    }
+
+    @Test
+    public void testFailQuery() throws SQLException, IOException {
+        QuerACLTestUtil.setUser(MODELER);
+        QuerACLTestUtil.mockQuery(PROJECT, "select * from STREAMING_TABLE");
+
+        QuerACLTestUtil.setUser(ADMIN);
+        
TableACLManager.getInstance(KylinConfig.getInstanceFromEnv()).addTableACL(PROJECT,
 "ADMIN", STREAMING_TABLE);
+        thrown.expectCause(CoreMatchers.isA(AccessDeniedException.class));
+        thrown.expectMessage(CoreMatchers.containsString("Query failed.Access 
table:DEFAULT.STREAMING_TABLE denied"));
+        QuerACLTestUtil.mockQuery(PROJECT, "select * from STREAMING_TABLE");
+    }
+
+    @After
+    public void after() throws Exception {
+        this.cleanupTestMetadata();
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/security/TableACLManagerTest.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/security/TableACLManagerTest.java 
b/server/src/test/java/org/apache/kylin/rest/security/TableACLManagerTest.java
new file mode 100644
index 0000000..c410487
--- /dev/null
+++ 
b/server/src/test/java/org/apache/kylin/rest/security/TableACLManagerTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.kylin.rest.security;
+
+import org.apache.kylin.metadata.acl.TableACLManager;
+import org.apache.kylin.rest.util.MultiNodeManagerTestBase;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TableACLManagerTest extends MultiNodeManagerTestBase {
+
+    @Test
+    public void test() throws Exception {
+        final TableACLManager tableACLManagerA = new TableACLManager(configA);
+        final TableACLManager tableACLManagerB = new TableACLManager(configB);
+
+        Assert.assertEquals(0, 
tableACLManagerB.getTableACLByCache(PROJECT).getUserTableBlackList().size());
+        tableACLManagerA.addTableACL(PROJECT, USER, TABLE);
+        // if don't sleep, manager B's get method is faster than notify
+        Thread.sleep(1000);
+        Assert.assertEquals(1, 
tableACLManagerB.getTableACLByCache(PROJECT).getUserTableBlackList().size());
+
+        Assert.assertEquals(1, 
tableACLManagerA.getTableACLByCache(PROJECT).getUserTableBlackList().size());
+        tableACLManagerB.deleteTableACL(PROJECT, USER, TABLE);
+        Thread.sleep(1000);
+        Assert.assertEquals(0, 
tableACLManagerA.getTableACLByCache(PROJECT).getUserTableBlackList().size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java 
b/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java
index 248d967..0d10d6f 100644
--- 
a/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java
+++ 
b/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java
@@ -19,8 +19,9 @@
 package org.apache.kylin.rest.service;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.kylin.metadata.acl.TableACL;
 import org.junit.Assert;
@@ -50,11 +51,11 @@ public class TableACLServiceTest extends ServiceTestBase {
         tableACLService.addToTableBlackList(PROJECT, "ANALYST", "DB.TABLE1");
         tableACLService.addToTableBlackList(PROJECT, "ANALYST", "DB.TABLE2");
         tableACLService.addToTableBlackList(PROJECT, "ANALYST", "DB.TABLE4");
-        List<String> tableBlackList = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.TABLE1");
+        List<String> tableBlackList = 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.TABLE1");
         Assert.assertEquals(3, tableBlackList.size());
 
         //test get black/white list
-        List<String> allUsers = new ArrayList<>();
+        Set<String> allUsers = new HashSet<>();
         allUsers.add("ADMIN");
         allUsers.add("MODELER");
         allUsers.add("ANALYST");
@@ -62,25 +63,30 @@ public class TableACLServiceTest extends ServiceTestBase {
         allUsers.add("user5");
         allUsers.add("user6");
         allUsers.add("user7");
-        List<String> tableWhiteList = 
tableACLService.getTableWhiteListByTable(PROJECT, "DB.TABLE1", allUsers);
+
+        List<String> tableWhiteList = 
tableACLService.getUsersCanQueryTheTbl(PROJECT, "DB.TABLE1", allUsers);
         Assert.assertEquals(4, tableWhiteList.size());
 
-        List<String> emptyTableBlackList = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.T");
+        List<String> emptyTableBlackList = 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.T");
         Assert.assertEquals(0, emptyTableBlackList.size());
 
-        List<String> tableWhiteList1 = 
tableACLService.getTableWhiteListByTable(PROJECT, "DB.T", allUsers);
+        List<String> tableWhiteList1 = 
tableACLService.getUsersCanQueryTheTbl(PROJECT, "DB.T", allUsers);
         Assert.assertEquals(7, tableWhiteList1.size());
 
         //test add
         tableACLService.addToTableBlackList(PROJECT, "user7", "DB.T7");
-        List<String> tableBlackList2 = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.T7");
+        List<String> tableBlackList2 = 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.T7");
         Assert.assertTrue(tableBlackList2.contains("user7"));
 
         //test delete
         tableACLService.deleteFromTableBlackList(PROJECT, "user7", "DB.T7");
-        List<String> tableBlackList3 = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.T7");
+        List<String> tableBlackList3 = 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.T7");
         Assert.assertFalse(tableBlackList3.contains("user7"));
 
+        //test delete
+        Assert.assertEquals(3, 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.TABLE1").size());
+        tableACLService.deleteFromTableBlackList(PROJECT, "ADMIN");
+        Assert.assertEquals(2, 
tableACLService.getUsersCannotQueryTheTbl(PROJECT, "DB.TABLE1").size());
     }
 
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/06138f51/server/src/test/java/org/apache/kylin/rest/util/MultiNodeManagerTestBase.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/util/MultiNodeManagerTestBase.java 
b/server/src/test/java/org/apache/kylin/rest/util/MultiNodeManagerTestBase.java
new file mode 100644
index 0000000..20baeca
--- /dev/null
+++ 
b/server/src/test/java/org/apache/kylin/rest/util/MultiNodeManagerTestBase.java
@@ -0,0 +1,97 @@
+/*
+ * 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.kylin.rest.util;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.CheckUtil;
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
+import org.apache.kylin.metadata.cachesync.Broadcaster;
+import org.apache.kylin.rest.broadcaster.BroadcasterReceiveServlet;
+import org.apache.kylin.rest.service.CacheService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MultiNodeManagerTestBase extends LocalFileMetadataTestCase {
+    private static Server server;
+    protected static String PROJECT = "default";
+    protected static String USER = "u1";
+    protected static String TABLE = "t1";
+
+    protected static KylinConfig configA;
+    protected static KylinConfig configB;
+    private static final Logger logger = 
LoggerFactory.getLogger(MultiNodeManagerTestBase.class);
+
+    @Before
+    public void setup() throws Exception {
+        staticCreateTestMetadata();
+
+        int port = CheckUtil.randomAvailablePort(40000, 50000);
+        logger.info("Chosen port for CacheServiceTest is " + port);
+        configA = KylinConfig.getInstanceFromEnv();
+        configA.setProperty("kylin.server.cluster-servers", "localhost:" + 
port);
+        configB = KylinConfig.createKylinConfig(configA);
+        configB.setProperty("kylin.server.cluster-servers", "localhost:" + 
port);
+        configB.setMetadataUrl("../examples/test_metadata");
+
+        server = new Server(port);
+        ServletContextHandler context = new 
ServletContextHandler(ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        final CacheService serviceA = new CacheService() {
+            @Override
+            public KylinConfig getConfig() {
+                return configA;
+            }
+        };
+        final CacheService serviceB = new CacheService() {
+            @Override
+            public KylinConfig getConfig() {
+                return configB;
+            }
+        };
+
+        context.addServlet(new ServletHolder(new BroadcasterReceiveServlet(new 
BroadcasterReceiveServlet.BroadcasterHandler() {
+            @Override
+            public void handle(String entity, String cacheKey, String event) {
+                Broadcaster.Event wipeEvent = 
Broadcaster.Event.getEvent(event);
+                final String log = "wipe cache type: " + entity + " event:" + 
wipeEvent + " name:" + cacheKey;
+                logger.info(log);
+                try {
+                    serviceA.notifyMetadataChange(entity, wipeEvent, cacheKey);
+                    serviceB.notifyMetadataChange(entity, wipeEvent, cacheKey);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        })), "/");
+        server.start();
+    }
+
+    @After
+    public void after() throws Exception {
+        server.stop();
+        cleanAfterClass();
+    }
+}

Reply via email to