diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/AccessControlUserConfigUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/AccessControlUserConfigUtils.java index a31439cb2c96..b910ca6187d8 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/AccessControlUserConfigUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/AccessControlUserConfigUtils.java @@ -46,6 +46,7 @@ public static UserConfig fromZNRecord(ZNRecord znRecord) { String role = simpleFields.get(UserConfig.ROLE_KEY); List tableList = znRecord.getListField(UserConfig.TABLES_KEY); + List excludeTableList = znRecord.getListField(UserConfig.EXCLUDE_TABLES_KEY); List permissionListFromZNRecord = znRecord.getListField(UserConfig.PERMISSIONS_KEY); List permissionList = null; @@ -53,7 +54,7 @@ public static UserConfig fromZNRecord(ZNRecord znRecord) { permissionList = permissionListFromZNRecord.stream() .map(x -> AccessType.valueOf(x)).collect(Collectors.toList()); } - return new UserConfig(username, password, component, role, tableList, permissionList); + return new UserConfig(username, password, component, role, tableList, excludeTableList, permissionList); } public static ZNRecord toZNRecord(UserConfig userConfig) @@ -73,6 +74,10 @@ public static ZNRecord toZNRecord(UserConfig userConfig) if (tableList != null) { listFields.put(UserConfig.TABLES_KEY, userConfig.getTables()); } + List excludeTableList = userConfig.getExcludeTables(); + if (excludeTableList != null) { + listFields.put(UserConfig.EXCLUDE_TABLES_KEY, userConfig.getExcludeTables()); + } List permissionList = userConfig.getPermissios(); if (permissionList != null) { diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java index 77762c2ee8fb..a2fb368beff4 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java @@ -1625,12 +1625,14 @@ public void initUserACLConfig(ControllerConf controllerConf) if (CollectionUtils.isEmpty(ZKMetadataProvider.getAllUserName(_propertyStore))) { String initUsername = controllerConf.getInitAccessControlUsername(); String initPassword = controllerConf.getInitAccessControlPassword(); - addUser(new UserConfig(initUsername, initPassword, ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), null, - null)); + addUser(new UserConfig(initUsername, initPassword, ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), + null, null, null)); addUser( - new UserConfig(initUsername, initPassword, ComponentType.BROKER.name(), RoleType.ADMIN.name(), null, null)); + new UserConfig(initUsername, initPassword, ComponentType.BROKER.name(), RoleType.ADMIN.name(), + null, null, null)); addUser( - new UserConfig(initUsername, initPassword, ComponentType.SERVER.name(), RoleType.ADMIN.name(), null, null)); + new UserConfig(initUsername, initPassword, ComponentType.SERVER.name(), RoleType.ADMIN.name(), + null, null, null)); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java index 20831a025756..f53fd8c49533 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java @@ -29,12 +29,15 @@ public class BasicAuthPrincipal { private final String _name; private final String _token; private final Set _tables; + private final Set _excludeTables; private final Set _permissions; - public BasicAuthPrincipal(String name, String token, Set tables, Set permissions) { + public BasicAuthPrincipal(String name, String token, Set tables, Set excludeTables, + Set permissions) { _name = name; _token = token; _tables = tables; + _excludeTables = excludeTables; _permissions = permissions.stream().map(s -> s.toLowerCase()).collect(Collectors.toSet()); } @@ -47,9 +50,17 @@ public String getToken() { } public boolean hasTable(String tableName) { + return isTableIncluded(tableName) && isTableNotExcluded(tableName); + } + + private boolean isTableIncluded(String tableName) { return _tables.isEmpty() || _tables.contains(tableName); } + private boolean isTableNotExcluded(String tableName) { + return !_excludeTables.contains(tableName); + } + public boolean hasPermission(String permission) { return _permissions.isEmpty() || _permissions.contains(permission.toLowerCase()); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java index ab4caa91b3ce..94f6b6bd95c4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java @@ -37,6 +37,7 @@ public final class BasicAuthUtils { private static final String PASSWORD = "password"; private static final String PERMISSIONS = "permissions"; private static final String TABLES = "tables"; + private static final String EXCLUDE_TABLES = "excludeTables"; private static final String ALL = "*"; private BasicAuthUtils() { @@ -72,10 +73,11 @@ public static List extractBasicAuthPrincipals(PinotConfigura Preconditions.checkArgument(StringUtils.isNotBlank(password), "must provide a password for %s", name); Set tables = extractSet(configuration, prefix + "." + name + "." + TABLES); + Set excludeTables = extractSet(configuration, prefix + "." + name + "." + EXCLUDE_TABLES); Set permissions = extractSet(configuration, prefix + "." + name + "." + PERMISSIONS); return new BasicAuthPrincipal(name, org.apache.pinot.common.auth.BasicAuthUtils.toBasicAuthToken(name, password), - tables, permissions); + tables, excludeTables, permissions); }).collect(Collectors.toList()); } @@ -92,13 +94,16 @@ public static List extractBasicAuthPrincipals(List tables = Optional.ofNullable(user.getTables()) .orElseGet(() -> Collections.emptyList()) .stream().collect(Collectors.toSet()); + Set excludeTables = Optional.ofNullable(user.getExcludeTables()) + .orElseGet(() -> Collections.emptyList()) + .stream().collect(Collectors.toSet()); Set permissions = Optional.ofNullable(user.getPermissios()) .orElseGet(() -> Collections.emptyList()) .stream().map(x -> x.toString()) .collect(Collectors.toSet()); return new ZkBasicAuthPrincipal(name, org.apache.pinot.common.auth.BasicAuthUtils.toBasicAuthToken(name, password), password, - component, role, tables, permissions); + component, role, tables, excludeTables, permissions); }).collect(Collectors.toList()); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/ZkBasicAuthPrincipal.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/ZkBasicAuthPrincipal.java index 93f97943ce5f..a4ee23e035f7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/ZkBasicAuthPrincipal.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/ZkBasicAuthPrincipal.java @@ -31,8 +31,8 @@ public class ZkBasicAuthPrincipal extends BasicAuthPrincipal { private final String _role; public ZkBasicAuthPrincipal(String name, String token, String password, String component, String role, - Set tables, Set permissions) { - super(name, token, tables, permissions); + Set tables, Set excludeTables, Set permissions) { + super(name, token, tables, excludeTables, permissions); _component = component; _role = role; _password = password; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/auth/BasicAuthTest.java b/pinot-core/src/test/java/org/apache/pinot/core/auth/BasicAuthTest.java index bcadc8b886d2..20897c8ee7cb 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/auth/BasicAuthTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/auth/BasicAuthTest.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.auth; import com.google.common.collect.ImmutableSet; +import java.util.Collections; import org.testng.Assert; import org.testng.annotations.Test; @@ -26,26 +27,40 @@ public class BasicAuthTest { @Test - public void testBasicAuthPrincipal() - throws Exception { - Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("READ")) + public void testBasicAuthPrincipal() { + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("READ")) .hasTable("myTable")); - Assert.assertTrue( - new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable", "myTable1"), ImmutableSet.of("Read")) - .hasTable("myTable1")); - Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("read")) + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable", "myTable1"), + Collections.emptySet(), ImmutableSet.of("Read")) .hasTable("myTable1")); - Assert.assertFalse( - new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable", "myTable1"), ImmutableSet.of("read")) - .hasTable("myTable2")); + Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("read")) + .hasTable("myTable1")); + Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable", "myTable1"), + Collections.emptySet(), ImmutableSet.of("read")) + .hasTable("myTable2")); + Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("myTable"), + ImmutableSet.of("read")) + .hasTable("myTable")); + Assert.assertFalse(new BasicAuthPrincipal("name", "token", Collections.emptySet(), ImmutableSet.of("myTable"), + ImmutableSet.of("read")) + .hasTable("myTable")); + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("myTable1"), + ImmutableSet.of("read")) + .hasTable("myTable")); - Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("READ")) + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("READ")) .hasPermission("read")); - Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("Read")) + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("Read")) .hasPermission("READ")); - Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("read")) + Assert.assertTrue(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("read")) .hasPermission("Read")); - Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), ImmutableSet.of("read")) + Assert.assertFalse(new BasicAuthPrincipal("name", "token", ImmutableSet.of("myTable"), Collections.emptySet(), + ImmutableSet.of("read")) .hasPermission("write")); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/auth/ZkBasicAuthTest.java b/pinot-core/src/test/java/org/apache/pinot/core/auth/ZkBasicAuthTest.java index 274505a543e4..8ddecec764b7 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/auth/ZkBasicAuthTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/auth/ZkBasicAuthTest.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.auth; import com.google.common.collect.ImmutableSet; +import java.util.Collections; import org.apache.pinot.spi.config.user.ComponentType; import org.apache.pinot.spi.config.user.RoleType; import org.testng.Assert; @@ -28,32 +29,40 @@ public class ZkBasicAuthTest { @Test - public void testBasicAuthPrincipal() - throws Exception { + public void testBasicAuthPrincipal() { Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("READ")).hasTable("myTable")); + Collections.emptySet(), ImmutableSet.of("READ")).hasTable("myTable")); Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable", "myTable1"), - ImmutableSet.of("Read")).hasTable("myTable1")); + Collections.emptySet(), ImmutableSet.of("Read")).hasTable("myTable1")); Assert.assertFalse(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("read")).hasTable("myTable1")); + Collections.emptySet(), ImmutableSet.of("read")).hasTable("myTable1")); Assert.assertFalse(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable", "myTable1"), - ImmutableSet.of("read")).hasTable("myTable2")); + Collections.emptySet(), ImmutableSet.of("read")).hasTable("myTable2")); + Assert.assertFalse(new ZkBasicAuthPrincipal("name", "token", "password", + ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable", "myTable1"), + ImmutableSet.of("myTable3"), ImmutableSet.of("Read")).hasTable("myTable3")); + Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", + ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable", "myTable1"), + ImmutableSet.of("myTable"), ImmutableSet.of("read")).hasTable("myTable1")); + Assert.assertFalse(new ZkBasicAuthPrincipal("name", "token", "password", + ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), Collections.emptySet(), + ImmutableSet.of("myTable"), ImmutableSet.of("read")).hasTable("myTable")); Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("READ")).hasPermission("read")); + Collections.emptySet(), ImmutableSet.of("READ")).hasPermission("read")); Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("Read")).hasPermission("READ")); + Collections.emptySet(), ImmutableSet.of("Read")).hasPermission("READ")); Assert.assertTrue(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("read")).hasPermission("Read")); + Collections.emptySet(), ImmutableSet.of("read")).hasPermission("Read")); Assert.assertFalse(new ZkBasicAuthPrincipal("name", "token", "password", ComponentType.CONTROLLER.name(), RoleType.ADMIN.name(), ImmutableSet.of("myTable"), - ImmutableSet.of("read")).hasPermission("write")); + Collections.emptySet(), ImmutableSet.of("read")).hasPermission("write")); } } diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/config/user/UserConfig.java b/pinot-spi/src/main/java/org/apache/pinot/spi/config/user/UserConfig.java index bfe9d0a8c961..cd05cd7d8381 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/config/user/UserConfig.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/config/user/UserConfig.java @@ -35,6 +35,7 @@ public class UserConfig extends BaseJsonConfig { public static final String ROLE_KEY = "role"; public static final String AUTH_TOKEN_KEY = "authToken"; public static final String TABLES_KEY = "tables"; + public static final String EXCLUDE_TABLES_KEY = "excludeTables"; public static final String PERMISSIONS_KEY = "permissions"; @JsonPropertyDescription("The name of User") @@ -52,6 +53,9 @@ public class UserConfig extends BaseJsonConfig { @JsonPropertyDescription("The tables owned of User") private List _tables; + @JsonPropertyDescription("The tables excluded for User") + private List _excludeTables; + @JsonPropertyDescription("The table permission of User") private List _permissions; @@ -61,6 +65,7 @@ public UserConfig(@JsonProperty(value = USERNAME_KEY, required = true) String us @JsonProperty(value = COMPONET_KEY, required = true) String component, @JsonProperty(value = ROLE_KEY, required = true) String role, @JsonProperty(value = TABLES_KEY) @Nullable List tableList, + @JsonProperty(value = EXCLUDE_TABLES_KEY) @Nullable List excludeTableList, @JsonProperty(value = PERMISSIONS_KEY) @Nullable List permissionList ) { Preconditions.checkArgument(username != null, "'username' must be configured"); @@ -72,6 +77,7 @@ public UserConfig(@JsonProperty(value = USERNAME_KEY, required = true) String us _componentType = ComponentType.valueOf(component.toUpperCase()); _roleType = RoleType.valueOf(role.toUpperCase()); _tables = tableList; + _excludeTables = excludeTableList; _permissions = permissionList; } @@ -98,6 +104,11 @@ public List getTables() { return _tables; } + @JsonProperty(EXCLUDE_TABLES_KEY) + public List getExcludeTables() { + return _excludeTables; + } + @JsonProperty(PERMISSIONS_KEY) public List getPermissios() { return _permissions; diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UserConfigBuilder.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UserConfigBuilder.java index 5afbf639ac9e..c2c70c9f75cb 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UserConfigBuilder.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UserConfigBuilder.java @@ -31,6 +31,7 @@ public class UserConfigBuilder { private String _password; private RoleType _roleType; private List _tableList; + private List _excludeTableList; private List _permissionList; public UserConfigBuilder setComponentType(ComponentType componentType) { @@ -58,6 +59,11 @@ public UserConfigBuilder setTableList(List tableList) { return this; } + public UserConfigBuilder setExcludeTableList(List excludeTableList) { + _excludeTableList = excludeTableList; + return this; + } + public UserConfigBuilder setPermissionList(List permissionList) { _permissionList = permissionList; return this; @@ -65,6 +71,6 @@ public UserConfigBuilder setPermissionList(List permissionList) { public UserConfig build() { return new UserConfig(_username, _password, _componentType.toString(), _roleType.toString(), _tableList, - _permissionList); + _excludeTableList, _permissionList); } }