KYLIN-2761 Table Level ACL

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

Branch: refs/heads/ranger
Commit: a5a8def893644ca2b0bcdd8e34ef251d3c028455
Parents: 2d939a5
Author: tttMelody <245915...@qq.com>
Authored: Tue Sep 5 15:11:14 2017 +0800
Committer: Hongbin Ma <m...@kyligence.io>
Committed: Wed Sep 6 10:49:28 2017 +0800

----------------------------------------------------------------------
 .../apache/kylin/common/KylinConfigBase.java    |   4 +
 .../org/apache/kylin/common/QueryContext.java   |   9 +
 .../main/resources/kylin-defaults.properties    |   3 +
 .../org/apache/kylin/metadata/acl/TableACL.java | 146 ++++++++++++++++
 .../kylin/metadata/acl/TableACLManager.java     | 114 +++++++++++++
 .../apache/kylin/metadata/model/ColumnDesc.java |   3 +-
 .../apache/kylin/metadata/model/TblColRef.java  |  17 +-
 .../metadata/model/tool/CalciteParser.java      |   9 +-
 .../apache/kylin/metadata/acl/TableACLTest.java |  55 ++++++
 .../relnode/OLAPToEnumerableConverter.java      |  18 ++
 .../query/security/AccessDeniedException.java   |  25 +++
 .../kylin/query/security/QueryIntercept.java    |  30 ++++
 .../query/security/QueryInterceptUtil.java      | 170 +++++++++++++++++++
 .../kylin/query/security/TableLevelACL.java     |  79 +++++++++
 .../kylin/query/util/PushDownUtilTest.java      |   7 +
 .../rest/controller2/TableAclControllerV2.java  |  99 +++++++++++
 .../kylin/rest/security/TableIntercept.java     |  66 +++++++
 .../apache/kylin/rest/service/BasicService.java |   5 +
 .../apache/kylin/rest/service/QueryService.java |   3 +
 .../kylin/rest/service/TableACLService.java     |  70 ++++++++
 .../apache/kylin/rest/util/ValidateUtil.java    | 107 ++++++++++++
 .../kylin/rest/service/TableACLServiceTest.java |  86 ++++++++++
 22 files changed, 1121 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
----------------------------------------------------------------------
diff --git 
a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java 
b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index 78edd67..7184553 100644
--- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -1038,6 +1038,10 @@ abstract public class KylinConfigBase implements 
Serializable {
         return getOptionalStringArray("kylin.query.transformers", new 
String[0]);
     }
 
+    public String[] getQueryIntercept() {
+        return getOptionalStringArray("kylin.query.intercepts", new String[0]);
+    }
+
     public long getQueryDurationCacheThreshold() {
         return 
Long.parseLong(this.getOptional("kylin.query.cache-threshold-duration", 
String.valueOf(2000)));
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
----------------------------------------------------------------------
diff --git 
a/core-common/src/main/java/org/apache/kylin/common/QueryContext.java 
b/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
index 0b8d519..9e0c33b 100644
--- a/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
+++ b/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
@@ -34,6 +34,7 @@ public class QueryContext {
     };
 
     private String queryId;
+    private String username;
     private AtomicLong scannedRows = new AtomicLong();
     private AtomicLong scannedBytes = new AtomicLong();
 
@@ -59,6 +60,14 @@ public class QueryContext {
         this.queryId = queryId;
     }
 
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
     public long getScannedRows() {
         return scannedRows.get();
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-common/src/main/resources/kylin-defaults.properties
----------------------------------------------------------------------
diff --git a/core-common/src/main/resources/kylin-defaults.properties 
b/core-common/src/main/resources/kylin-defaults.properties
index f0328fa..83ba320 100644
--- a/core-common/src/main/resources/kylin-defaults.properties
+++ b/core-common/src/main/resources/kylin-defaults.properties
@@ -253,3 +253,6 @@ 
kylin.engine.spark-conf.spark.hadoop.yarn.timeline-service.enabled=false
 #kylin.query.pushdown.jdbc.pool-max-total=8
 #kylin.query.pushdown.jdbc.pool-max-idle=8
 #kylin.query.pushdown.jdbc.pool-min-idle=0
+
+### TABLE ACL
+kylin.query.intercepts=org.apache.kylin.rest.security.TableIntercept
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..6d6f158
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACL.java
@@ -0,0 +1,146 @@
+/*
+ * 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.metadata.acl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.kylin.common.persistence.RootPersistentEntity;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@SuppressWarnings("serial")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
+        getterVisibility = JsonAutoDetect.Visibility.NONE,
+        isGetterVisibility = JsonAutoDetect.Visibility.NONE,
+        setterVisibility = JsonAutoDetect.Visibility.NONE)
+public class TableACL extends RootPersistentEntity {
+
+    //user1 : [DB.TABLE1, DB.TABLE2], means that user1 can not query 
DB.TABLE1, DB.TABLE2
+    @JsonProperty()
+    private Map<String, TableBlackList> userTableBlackList;
+
+    public TableACL() {
+        userTableBlackList = new HashMap<>();
+    }
+
+    public Map<String, TableBlackList> getUserTableBlackList() {
+        return userTableBlackList;
+    }
+
+    public List<String> getTableBlackList(String username) {
+        TableBlackList tableBlackList = userTableBlackList.get(username);
+        if (tableBlackList == null) {
+            tableBlackList = new TableBlackList();
+        }
+        return tableBlackList.getTables();
+    }
+
+    //get users that can not query the table
+    public List<String> getBlockedUserByTable(String table) {
+        List<String> results = new ArrayList<>();
+        for (String user : userTableBlackList.keySet()) {
+            TableBlackList tables = userTableBlackList.get(user);
+            if (tables.contains(table)) {
+                results.add(user);
+            }
+        }
+        return results;
+    }
+
+    public TableACL add(String username, String table) {
+        if (userTableBlackList == null) {
+            userTableBlackList = new HashMap<>();
+        }
+        TableBlackList tableBlackList = userTableBlackList.get(username);
+
+        if (tableBlackList == null) {
+            tableBlackList = new TableBlackList();
+            userTableBlackList.put(username, tableBlackList);
+        }
+
+        //before add, check exists
+        checkACLExists(username, table, tableBlackList);
+        tableBlackList.add(table);
+        return this;
+    }
+
+    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
+                    + ":" + username + " already exists!");
+        }
+    }
+
+    public TableACL delete(String username, String table) {
+        if (isTableInBlackList(username, 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,
+            getterVisibility = JsonAutoDetect.Visibility.NONE,
+            isGetterVisibility = JsonAutoDetect.Visibility.NONE,
+            setterVisibility = JsonAutoDetect.Visibility.NONE)
+    static class TableBlackList {
+        @JsonProperty()
+        List<String> tables;
+
+        TableBlackList() {
+            tables = new ArrayList<>();
+        }
+
+        public int size() {
+            return tables.size();
+        }
+
+        public boolean isEmpty() {
+            return tables.isEmpty();
+        }
+
+        public boolean contains(String s) {
+            return tables.contains(s);
+        }
+
+        public boolean add(String s) {
+            return tables.add(s);
+        }
+
+        public boolean remove(String s) {
+            return tables.remove(s);
+        }
+
+        public List<String> getTables() {
+            return tables;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..53c0843
--- /dev/null
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/TableACLManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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.metadata.acl;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ */
+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 String DIR_PREFIX = "/table_acl/";
+
+    // static cached instances
+    private static final ConcurrentMap<KylinConfig, TableACLManager> CACHE = 
new ConcurrentHashMap<>();
+
+    public static TableACLManager getInstance(KylinConfig config) {
+        TableACLManager r = CACHE.get(config);
+        if (r != null) {
+            return r;
+        }
+
+        synchronized (TableACLManager.class) {
+            r = CACHE.get(config);
+            if (r != null) {
+                return r;
+            }
+            try {
+                r = new TableACLManager(config);
+                CACHE.put(config, r);
+                if (CACHE.size() > 1) {
+                    logger.warn("More than one singleton exist");
+                }
+                return r;
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to init 
CubeDescManager from " + config, e);
+            }
+        }
+    }
+
+    public static void clearCache() {
+        CACHE.clear();
+    }
+
+    public static void clearCache(KylinConfig kylinConfig) {
+        if (kylinConfig != null)
+            CACHE.remove(kylinConfig);
+    }
+
+    // 
============================================================================
+
+    private KylinConfig config;
+
+    private TableACLManager(KylinConfig config) throws IOException {
+        this.config = config;
+    }
+
+    public KylinConfig getConfig() {
+        return config;
+    }
+
+    public ResourceStore getStore() {
+        return ResourceStore.getStore(this.config);
+    }
+
+    public 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) {
+            return new TableACL();
+        }
+        return tableACLRecord;
+    }
+
+    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);
+    }
+
+    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);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
index 394300e..5848838 100644
--- 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
@@ -193,9 +193,8 @@ public class ColumnDesc implements Serializable {
         return index;
     }
 
-    public String getComputedColumnExpr(String tableAlias) {
+    public String getComputedColumnExpr() {
         Preconditions.checkState(computedColumnExpr != null);
-        Preconditions.checkState(tableAlias != null);
 
         return computedColumnExpr;
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
index e7250a3..cc2dd03 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
@@ -158,7 +158,7 @@ public class TblColRef implements Serializable {
         if (!column.isComputedColumnn()) {
             return getIdentity();
         } else {
-            return column.getComputedColumnExpr(getTableAlias());
+            return column.getComputedColumnExpr();
         }
     }
 
@@ -243,4 +243,19 @@ public class TblColRef implements Serializable {
         }
     }
 
+    // return DB.TABLE
+    public String getTableWithSchema() {
+        if (isInnerColumn() && parserDescription != null)
+            return parserDescription;
+        if (column.getTable() == null) {
+            return "NULL";
+        } else {
+            return column.getTable().getIdentity().toUpperCase();
+        }
+    }
+
+    // return DB.TABLE.COLUMN
+    public String getColumWithTableAndSchema() {
+        return (getTableWithSchema() + "." + column.getName()).toUpperCase();
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
index fdad33a..c49b037 100644
--- 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
@@ -32,11 +32,11 @@ import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.util.SqlBasicVisitor;
 import org.apache.calcite.sql.util.SqlVisitor;
+import org.apache.kylin.common.util.Pair;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import org.apache.kylin.common.util.Pair;
 
 public class CalciteParser {
     public static SqlNode parse(String sql) throws SqlParseException {
@@ -64,6 +64,13 @@ public class CalciteParser {
         return getOnlySelectNode("select " + expr + " from t");
     }
 
+    public static String getLastNthName(SqlIdentifier id, int n) {
+        //n = 1 is getting column
+        //n = 2 is getting table's alias, if has.
+        //n = 3 is getting database name, if has.
+        return id.names.get(id.names.size() - n).replace("\"", 
"").toUpperCase();
+    }
+
     public static void ensureNoAliasInExpr(String expr) {
         SqlNode sqlNode = getExpNode(expr);
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..cfcfbf4
--- /dev/null
+++ 
b/core-metadata/src/test/java/org/apache/kylin/metadata/acl/TableACLTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.metadata.acl;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TableACLTest {
+
+    @Test
+    public void testTableACL() {
+        TableACL empty = new TableACL();
+        try {
+            empty.delete("a", "b");
+        } catch (Exception e) {
+            Assert.assertEquals(
+                    "Operation fail, can not grant user table query 
permission.Table ACL b:a is not found!",
+                    e.getMessage());
+        }
+
+        //add
+        TableACL tableACL = new TableACL();
+        tableACL.add("user1", "table1");
+        Assert.assertEquals(1, tableACL.getUserTableBlackList().size());
+
+        //add duplicated
+        try {
+            tableACL.add("user1", "table1");
+        } catch (Exception e) {
+            Assert.assertEquals(
+                    "Operation fail, can not revoke user's table query 
permission.Table ACL table1:user1 already exists!",
+                    e.getMessage());
+        }
+
+        //add
+        tableACL.add("user2", "table1");
+        Assert.assertEquals(2, tableACL.getUserTableBlackList().size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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 c7b0fe2..db934cf 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
@@ -35,6 +35,8 @@ import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.ClassUtil;
 import org.apache.kylin.query.routing.RealizationChooser;
+import org.apache.kylin.query.security.QueryIntercept;
+import org.apache.kylin.query.security.QueryInterceptUtil;
 
 import com.google.common.collect.Lists;
 
@@ -71,6 +73,15 @@ public class OLAPToEnumerableConverter extends ConverterImpl 
implements Enumerab
 
         // identify model & realization
         List<OLAPContext> contexts = listContextsHavingScan();
+
+        String project = getProject(contexts);
+        String user = getUser(contexts);
+
+        List<QueryIntercept> intercepts = 
QueryInterceptUtil.getQueryIntercepts();
+        for (QueryIntercept intercept : intercepts) {
+            intercept.intercept(project, user, contexts);
+        }
+
         RealizationChooser.selectRealization(contexts);
 
         doAccessControl(contexts);
@@ -116,4 +127,11 @@ public class OLAPToEnumerableConverter extends 
ConverterImpl implements Enumerab
         }
     }
 
+    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/a5a8def8/query/src/main/java/org/apache/kylin/query/security/AccessDeniedException.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/security/AccessDeniedException.java
 
b/query/src/main/java/org/apache/kylin/query/security/AccessDeniedException.java
new file mode 100644
index 0000000..de8cd45
--- /dev/null
+++ 
b/query/src/main/java/org/apache/kylin/query/security/AccessDeniedException.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+public class AccessDeniedException extends RuntimeException {
+    public AccessDeniedException(String s) {
+        super("Query failed.Access " + s + " denied");
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..493cc25
--- /dev/null
+++ b/query/src/main/java/org/apache/kylin/query/security/QueryIntercept.java
@@ -0,0 +1,30 @@
+/*
+ * 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 java.util.Collection;
+import java.util.List;
+
+import org.apache.kylin.query.relnode.OLAPContext;
+
+public interface QueryIntercept {
+    void intercept(String project, String username, List<OLAPContext> 
contexts);
+
+    Collection<String> getQueryIdentifiers(List<OLAPContext> contexts);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/query/src/main/java/org/apache/kylin/query/security/QueryInterceptUtil.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/security/QueryInterceptUtil.java 
b/query/src/main/java/org/apache/kylin/query/security/QueryInterceptUtil.java
new file mode 100644
index 0000000..ecfa8da
--- /dev/null
+++ 
b/query/src/main/java/org/apache/kylin/query/security/QueryInterceptUtil.java
@@ -0,0 +1,170 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.util.SqlBasicVisitor;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.ClassUtil;
+import org.apache.kylin.common.util.Pair;
+import org.apache.kylin.metadata.MetadataManager;
+import org.apache.kylin.metadata.model.ColumnDesc;
+import org.apache.kylin.metadata.model.DataModelDesc;
+import org.apache.kylin.metadata.model.TblColRef;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
+import org.apache.kylin.query.relnode.OLAPContext;
+
+import com.google.common.base.Preconditions;
+
+public class QueryInterceptUtil {
+    private static List<QueryIntercept> queryIntercepts = new ArrayList<>();
+
+    private static void setQueryIntercept() {
+        if (queryIntercepts.size() > 0) {
+            return;
+        }
+        String[] classes = 
KylinConfig.getInstanceFromEnv().getQueryIntercept();
+        for (String clz : classes) {
+            try {
+                QueryIntercept i = (QueryIntercept) ClassUtil.newInstance(clz);
+                queryIntercepts.add(i);
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to load query intercept", 
e);
+            }
+        }
+    }
+
+    public static List<QueryIntercept> getQueryIntercepts() {
+        setQueryIntercept();
+        return queryIntercepts;
+    }
+
+    public static Set<String> getAllColsWithTblAndSchema(String project, 
List<OLAPContext> contexts) {
+        // all columns with table and DB. Like DB.TABLE.COLUMN
+        Set<String> allColWithTblAndSchema = new HashSet<>();
+
+        for (OLAPContext context : contexts) {
+            for (TblColRef tblColRef : context.allColumns) {
+                ColumnDesc columnDesc = tblColRef.getColumnDesc();
+                //computed column
+                if (columnDesc.isComputedColumnn()) {
+                    allColWithTblAndSchema.addAll(getCCUsedCols(project, 
columnDesc));
+                    continue;
+                }
+                //normal column
+                
allColWithTblAndSchema.add(tblColRef.getColumWithTableAndSchema());
+            }
+        }
+        return allColWithTblAndSchema;
+    }
+
+    private static Set<String> getCCUsedCols(String project, ColumnDesc 
columnDesc) {
+        Set<String> usedCols = new HashSet<>();
+        Map<String, String> aliasTableMap = getAliasTableMap(project, 
columnDesc.getName());
+        Preconditions.checkState(aliasTableMap.size() > 0, "can not find cc:" 
+ columnDesc.getName() + "'s table alias");
+
+        List<Pair<String, String>> colsWithAlias = 
ExprIdentifierFinder.getExprIdentifiers(columnDesc.getComputedColumnExpr());
+        for (Pair<String, String> cols : colsWithAlias) {
+            String tableIdentifier = aliasTableMap.get(cols.getFirst());
+            usedCols.add(tableIdentifier + "." + cols.getSecond());
+        }
+        //Preconditions.checkState(usedCols.size() > 0, "can not find cc:" + 
columnDesc.getName() + "'s used cols");
+        return usedCols;
+    }
+
+    private static  Map<String, String> getAliasTableMap(String project, 
String ccName) {
+        DataModelDesc model = getModel(project, ccName);
+        Map<String, String> tableWithAlias = new HashMap<>();
+        for (String alias : model.getAliasMap().keySet()) {
+            String tableName = 
model.getAliasMap().get(alias).getTableDesc().getIdentity();
+            tableWithAlias.put(alias, tableName);
+        }
+        return tableWithAlias;
+    }
+
+    private static  DataModelDesc getModel(String project, String ccName) {
+        List<DataModelDesc> models = 
MetadataManager.getInstance(KylinConfig.getInstanceFromEnv()).getModels(project);
+        for (DataModelDesc model : models) {
+            Set<String> computedColumnNames = model.getComputedColumnNames();
+            if (computedColumnNames.contains(ccName)) {
+                return model;
+            }
+        }
+        return null;
+    }
+
+    static class ExprIdentifierFinder extends SqlBasicVisitor<SqlNode> {
+        List<Pair<String, String>> columnWithTableAlias;
+
+        ExprIdentifierFinder() {
+            this.columnWithTableAlias = new ArrayList<>();
+        }
+
+        List<Pair<String, String>> getIdentifiers() {
+            return columnWithTableAlias;
+        }
+
+        static List<Pair<String, String>> getExprIdentifiers(String expr) {
+            SqlNode exprNode = CalciteParser.getExpNode(expr);
+            ExprIdentifierFinder id = new ExprIdentifierFinder();
+            exprNode.accept(id);
+            return id.getIdentifiers();
+        }
+
+        @Override
+        public SqlNode visit(SqlCall call) {
+            for (SqlNode operand : call.getOperandList()) {
+                if (operand != null) {
+                    operand.accept(this);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public SqlNode visit(SqlIdentifier id) {
+            //Preconditions.checkState(id.names.size() == 2, "error when get 
identifier in cc's expr");
+            if (id.names.size() == 2) {
+                columnWithTableAlias.add(Pair.newPair(id.names.get(0), 
id.names.get(1)));
+            }
+            return null;
+        }
+    }
+
+    public static Set<String> getAllTblsWithSchema(List<OLAPContext> contexts) 
{
+        // all tables with DB, Like DB.TABLE, may have same table, so use set.
+        Set<String> tableWithSchema = new HashSet<>();
+        for (OLAPContext context : contexts) {
+            Set<TblColRef> allColumns = context.allColumns;
+            for (TblColRef tblColRef : allColumns) {
+                tableWithSchema.add(tblColRef.getTableWithSchema());
+            }
+        }
+        return tableWithSchema;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/query/src/main/java/org/apache/kylin/query/security/TableLevelACL.java
----------------------------------------------------------------------
diff --git 
a/query/src/main/java/org/apache/kylin/query/security/TableLevelACL.java 
b/query/src/main/java/org/apache/kylin/query/security/TableLevelACL.java
new file mode 100644
index 0000000..c1b67b0
--- /dev/null
+++ b/query/src/main/java/org/apache/kylin/query/security/TableLevelACL.java
@@ -0,0 +1,79 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.kylin.metadata.model.TblColRef;
+import org.apache.kylin.query.relnode.OLAPContext;
+
+public class TableLevelACL {
+    public static void tableFilter(List<OLAPContext> contexts, List<String> 
tableBlackList) {
+        Set<String> tableWithSchema = getTableWithSchema(contexts);
+        for (String tbl : tableBlackList) {
+            if (tableWithSchema.contains(tbl.toUpperCase())) {
+//                throw new kylin.AccessDeniedException("table:" + tbl);
+                System.out.println("Access table:" + tbl + " denied");
+            }
+        }
+    }
+
+    public static void columnFilter(List<OLAPContext> contexts, List<String> 
columnBlackList) {
+        List<String> allColWithTblAndSchema = 
getAllColWithTblAndSchema(contexts);
+        for (String tbl : columnBlackList) {
+            if (allColWithTblAndSchema.contains(tbl.toUpperCase())) {
+//                throw new kylin.AccessDeniedException("table:" + tbl);
+                System.out.println("Access table:" + tbl + " denied");
+            }
+        }
+    }
+
+    public static List<String> getAllColWithTblAndSchema(List<OLAPContext> 
contexts) {
+        // all columns with table and DB. Like DB.TABLE.COLUMN
+        List<String> allColWithTblAndSchema = new ArrayList<>();
+        for (OLAPContext context : contexts) {
+            Set<TblColRef> allColumns = context.allColumns;
+            for (TblColRef tblColRef : allColumns) {
+                
allColWithTblAndSchema.add(tblColRef.getColumWithTableAndSchema());
+            }
+        }
+        return allColWithTblAndSchema;
+    }
+
+    public static Set<String> getTableWithSchema(List<OLAPContext> contexts) {
+        // all tables with DB, Like DB.TABLE
+        Set<String> tableWithSchema = new HashSet<>();
+        for (OLAPContext context : contexts) {
+            Set<TblColRef> allColumns = context.allColumns;
+            for (TblColRef tblColRef : allColumns) {
+                tableWithSchema.add(tblColRef.getTableWithSchema());
+            }
+        }
+        return tableWithSchema;
+    }
+
+    public static List<String> mockTableBlackList() {
+        List<String> blackList = new ArrayList<>();
+        blackList.add("DEFAULT.KYLIN_SALES");
+        return blackList;
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
----------------------------------------------------------------------
diff --git 
a/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java 
b/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
index 63594bf..7802076 100644
--- a/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
+++ b/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
@@ -86,4 +86,11 @@ public class PushDownUtilTest {
                 + "ON at1.c = t3.c " + "WHERE t3.d > 0 " + "ORDER BY t3.e";
         Assert.assertEquals(exceptSQL, PushDownUtil.schemaCompletion(sql, 
"EDW"));
     }
+
+    @Test
+    public void testSchemaCompletionWithJoin() throws SqlParseException {
+        String sql = "select * from t1 join (select * from t2 join (select * 
from t3))";
+        String exceptSQL = "select * from EDW.t1 join (select * from EDW.t2 
join (select * from EDW.t3))";
+        Assert.assertEquals(exceptSQL, PushDownUtil.schemaCompletion(sql, 
"EDW"));
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/server-base/src/main/java/org/apache/kylin/rest/controller2/TableAclControllerV2.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/controller2/TableAclControllerV2.java
 
b/server-base/src/main/java/org/apache/kylin/rest/controller2/TableAclControllerV2.java
new file mode 100644
index 0000000..a72efa2
--- /dev/null
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/controller2/TableAclControllerV2.java
@@ -0,0 +1,99 @@
+/*
+ * 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.controller2;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.kylin.rest.controller.BasicController;
+import org.apache.kylin.rest.response.EnvelopeResponse;
+import org.apache.kylin.rest.response.ResponseCode;
+import org.apache.kylin.rest.service.TableACLService;
+import org.apache.kylin.rest.util.ValidateUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping(value = "/acl")
+public class TableAclControllerV2 extends BasicController {
+
+    @Autowired
+    @Qualifier("TableAclService")
+    private TableACLService tableACLService;
+
+    @Autowired
+    @Qualifier("validateUtil")
+    private ValidateUtil validateUtil;
+
+    @RequestMapping(value = "/table/{project}/{table:.+}", method = 
{RequestMethod.GET}, produces = {"application/vnd.apache.kylin-v2+json"})
+    @ResponseBody
+    public EnvelopeResponse getTableWhiteListByTable(@PathVariable String 
project, @PathVariable String table) throws IOException {
+        validateUtil.vaildateArgs(project, table);
+        project = project.toUpperCase();
+        validateUtil.validateTable(project, table);
+        List<String> allUsers = validateUtil.getAllUsers();
+        List<String> whiteList = 
tableACLService.getTableWhiteListByTable(project, table, allUsers);
+        return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, whiteList, "get 
table acl");
+    }
+
+    @RequestMapping(value = "/table/{project}/black/{table:.+}", method = 
{RequestMethod.GET}, produces = {"application/vnd.apache.kylin-v2+json"})
+    @ResponseBody
+    public EnvelopeResponse getTableBlackListByTable(@PathVariable String 
project, @PathVariable String table) throws IOException {
+        validateUtil.vaildateArgs(project, table);
+        project = project.toUpperCase();
+        validateUtil.validateTable(project, table);
+        List<String> blackList = 
tableACLService.getBlockedUserByTable(project, table);
+        return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, blackList, "get 
table acl");
+    }
+
+    // because the frontend passes user can not visit, so that means put it to 
the table black list
+    @RequestMapping(value = "/table/{project}/{table}/{username}", method = 
{RequestMethod.DELETE}, produces = {"application/vnd.apache.kylin-v2+json"})
+    @ResponseBody
+    public EnvelopeResponse putUserToTableBlackList(
+            @PathVariable String project,
+            @PathVariable String table,
+            @PathVariable String username) throws IOException {
+        validateUtil.vaildateArgs(project, table, username);
+        project = project.toUpperCase();
+        validateUtil.validateUser(username);
+        validateUtil.validateTable(project, table);
+        tableACLService.addToTableBlackList(project, username, table);
+        return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, "", "revoke 
user table query permission and add user to table black list.");
+    }
+
+    // because the frontend passes user can visit, so that means remove the 
user from the table black list
+    @RequestMapping(value = "/table/{project}/{table}/{username}", method = 
{RequestMethod.POST}, produces = {"application/vnd.apache.kylin-v2+json"})
+    @ResponseBody
+    public EnvelopeResponse deleteUserFromTableBlackList(
+            @PathVariable String project,
+            @PathVariable String table,
+            @PathVariable String username) throws IOException {
+        validateUtil.vaildateArgs(project, table, username);
+        project = project.toUpperCase();
+        validateUtil.validateUser(username);
+        validateUtil.validateTable(project, table);
+        tableACLService.deleteFromTableBlackList(project, username, table);
+        return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, "", "grant user 
table query permission and remove user from table black list.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..a980658
--- /dev/null
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/security/TableIntercept.java
@@ -0,0 +1,66 @@
+/*
+ * 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 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);
+
+    @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);
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getQueryIdentifiers(List<OLAPContext> contexts) {
+        return QueryInterceptUtil.getAllTblsWithSchema(contexts);
+    }
+
+    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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java 
b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
index 66e7cfb..f662042 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
@@ -25,6 +25,7 @@ import org.apache.kylin.cube.CubeDescManager;
 import org.apache.kylin.cube.CubeManager;
 import org.apache.kylin.job.execution.ExecutableManager;
 import org.apache.kylin.metadata.MetadataManager;
+import org.apache.kylin.metadata.acl.TableACLManager;
 import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
 import org.apache.kylin.metadata.draft.DraftManager;
 import org.apache.kylin.metadata.project.ProjectManager;
@@ -84,4 +85,8 @@ public abstract class BasicService {
         return DraftManager.getInstance(getConfig());
     }
 
+    public TableACLManager getTableACLManager() {
+        return TableACLManager.getInstance(getConfig());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java 
b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
index 51ec902..f57aeb1 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
@@ -509,6 +509,8 @@ public class QueryService extends BasicService {
             conn = QueryConnection.getConnection(sqlRequest.getProject());
 
             String userInfo = 
SecurityContextHolder.getContext().getAuthentication().getName();
+            QueryContext context = QueryContext.current();
+            context.setUsername(userInfo);
             final Collection<? extends GrantedAuthority> grantedAuthorities = 
SecurityContextHolder.getContext()
                     .getAuthentication().getAuthorities();
             for (GrantedAuthority grantedAuthority : grantedAuthorities) {
@@ -842,6 +844,7 @@ public class QueryService extends BasicService {
             close(resultSet, stat, null); //conn is passed in, not my duty to 
close
         }
 
+
         return buildSqlResponse(isPushDown, results, columnMetas);
     }
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..3f1b515
--- /dev/null
+++ 
b/server-base/src/main/java/org/apache/kylin/rest/service/TableACLService.java
@@ -0,0 +1,70 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.kylin.metadata.acl.TableACL;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component("TableAclService")
+public class TableACLService extends BasicService {
+    private static final Logger logger = 
LoggerFactory.getLogger(TableACLService.class);
+
+    @Autowired
+    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)
+            throws IOException {
+        List<String> blockedUsers = getBlockedUserByTable(project, table);
+        List<String> whiteUsers = new ArrayList<>();
+        for (String u : allUsers) {
+            if (!blockedUsers.contains(u)) {
+                whiteUsers.add(u);
+            }
+        }
+        return whiteUsers;
+    }
+
+    public TableACL getTableACLByProject(String project) throws IOException {
+        return getTableACLManager().getTableACL(project);
+    }
+
+    public List<String> getBlockedUserByTable(String project, String table) 
throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
+        return getTableACLByProject(project).getBlockedUserByTable(table);
+    }
+
+    public void addToTableBlackList(String project, String username, String 
table) throws IOException {
+        aclEvaluate.checkProjectAdminPermission(project);
+        getTableACLManager().addTableACL(project, username, table);
+    }
+
+    public void deleteFromTableBlackList(String project, String username, 
String table) throws IOException {
+        aclEvaluate.checkProjectAdminPermission(project);
+        getTableACLManager().deleteTableACL(project, username, table);
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..a95bdd4
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/ValidateUtil.java
@@ -0,0 +1,107 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.metadata.model.ColumnDesc;
+import org.apache.kylin.metadata.model.TableDesc;
+import org.apache.kylin.rest.security.ManagedUser;
+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.stereotype.Component;
+
+import com.google.common.base.Preconditions;
+
+@Component("validateUtil")
+public class ValidateUtil {
+
+    @Autowired
+    @Qualifier("userService")
+    private UserService userService;
+
+    @Autowired
+    @Qualifier("tableService")
+    private TableService tableService;
+
+    public void validateUser(String username) {
+        if (!userService.userExists(username)) {
+            throw new RuntimeException("Operation failed, user:" + username + 
" not exists");
+        }
+    }
+
+    public void validateTable(String project, String table) throws IOException 
{
+        List<TableDesc> tableDescs = 
tableService.getTableDescByProject(project, false);
+        List<String> tables = new ArrayList<>();
+        for (TableDesc tableDesc : tableDescs) {
+            tables.add(tableDesc.getDatabase() + "." + tableDesc.getName());
+        }
+
+        if (!tables.contains(table)) {
+            throw new RuntimeException("Operation failed, table:" + table + " 
not exists");
+        }
+    }
+
+    public void validateColumn(String project, String table, 
Collection<String> columns) throws IOException {
+        Preconditions.checkState(columns != null && columns.size() > 0);
+        List<String> cols = getAllColumns(project, table);
+        for (String c : columns) {
+            if (!cols.contains(c)) {
+                throw new RuntimeException("Operation failed, column:" + c + " 
not exists");
+            }
+        }
+    }
+
+    private List<String> getAllColumns(String project, String table) throws 
IOException {
+        List<TableDesc> tableDescByProject = 
tableService.getTableDescByProject(project, true);
+        List<String> cols = new ArrayList<>();
+
+        for (TableDesc tableDesc : tableDescByProject) {
+            String tbl = tableDesc.getDatabase() + "." + tableDesc.getName();
+            if (tbl.equals(table)) {
+                for (ColumnDesc column : tableDesc.getColumns()) {
+                    cols.add(column.getName());
+                }
+                break;
+            }
+        }
+        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());
+        }
+        return allUsers;
+    }
+
+    public void vaildateArgs(String... args) {
+        for (String arg : args) {
+            Preconditions.checkState(!StringUtils.isEmpty(arg));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/a5a8def8/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
new file mode 100644
index 0000000..248d967
--- /dev/null
+++ 
b/server/src/test/java/org/apache/kylin/rest/service/TableACLServiceTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.kylin.metadata.acl.TableACL;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+public class TableACLServiceTest extends ServiceTestBase {
+    private final static String PROJECT = "learn_kylin";
+
+    @Autowired
+    @Qualifier("TableAclService")
+    private TableACLService tableACLService;
+
+    @Test
+    public void testTableACL() throws IOException {
+        TableACL emptyBlackList = 
tableACLService.getTableACLByProject(PROJECT);
+        Assert.assertEquals(0, emptyBlackList.getUserTableBlackList().size());
+
+        tableACLService.addToTableBlackList(PROJECT, "ADMIN", "DB.TABLE");
+        tableACLService.addToTableBlackList(PROJECT, "ADMIN", "DB.TABLE1");
+        tableACLService.addToTableBlackList(PROJECT, "ADMIN", "DB.TABLE2");
+        tableACLService.addToTableBlackList(PROJECT, "MODELER", "DB.TABLE4");
+        tableACLService.addToTableBlackList(PROJECT, "MODELER", "DB.TABLE1");
+        tableACLService.addToTableBlackList(PROJECT, "MODELER", "DB.TABLE");
+        tableACLService.addToTableBlackList(PROJECT, "ANALYST", "DB.TABLE");
+        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");
+        Assert.assertEquals(3, tableBlackList.size());
+
+        //test get black/white list
+        List<String> allUsers = new ArrayList<>();
+        allUsers.add("ADMIN");
+        allUsers.add("MODELER");
+        allUsers.add("ANALYST");
+        allUsers.add("user4");
+        allUsers.add("user5");
+        allUsers.add("user6");
+        allUsers.add("user7");
+        List<String> tableWhiteList = 
tableACLService.getTableWhiteListByTable(PROJECT, "DB.TABLE1", allUsers);
+        Assert.assertEquals(4, tableWhiteList.size());
+
+        List<String> emptyTableBlackList = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.T");
+        Assert.assertEquals(0, emptyTableBlackList.size());
+
+        List<String> tableWhiteList1 = 
tableACLService.getTableWhiteListByTable(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");
+        Assert.assertTrue(tableBlackList2.contains("user7"));
+
+        //test delete
+        tableACLService.deleteFromTableBlackList(PROJECT, "user7", "DB.T7");
+        List<String> tableBlackList3 = 
tableACLService.getBlockedUserByTable(PROJECT, "DB.T7");
+        Assert.assertFalse(tableBlackList3.contains("user7"));
+
+    }
+
+}

Reply via email to