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(); + } +}