This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.1 by this push:
new b09efddf8fa branch-3.1: [feat](Authorization-plugin)Authorization
framework modularization #40750 #41100 #41574 (#52577)
b09efddf8fa is described below
commit b09efddf8fab0413d4452faae911dc283d4c2cb6
Author: Calvin Kirs <[email protected]>
AuthorDate: Wed Jul 2 10:55:29 2025 +0800
branch-3.1: [feat](Authorization-plugin)Authorization framework
modularization #40750 #41100 #41574 (#52577)
Cherry-picked from #40750 #41100 #41574
---
.../main/java/org/apache/doris/common/Config.java | 21 +++
.../ranger/doris/RangerDorisAccessController.java | 19 +--
.../hive/RangerHiveAccessControllerFactory.java | 6 +
.../doris/common/util/ChildFirstClassLoader.java | 151 +++++++++++++++++++++
.../apache/doris/common/util/ClassLoaderUtils.java | 126 +++++++++++++++++
.../apache/doris/common/util/PropertyAnalyzer.java | 11 --
.../mysql/authenticate/AuthenticatorManager.java | 39 +++---
.../mysql/privilege/AccessControllerFactory.java | 8 ++
.../mysql/privilege/AccessControllerManager.java | 104 ++++++++++----
...ava => RangerDorisAccessControllerFactory.java} | 13 +-
.../doris/mysql/privilege/UserPropertyMgr.java | 68 ++++++----
.../org/apache/doris/plugin/PropertiesUtils.java | 68 ++++++++++
...e.doris.mysql.privilege.AccessControllerFactory | 19 +++
.../privileges/CustomAccessControllerFactory.java} | 11 +-
.../nereids/privileges/TestCheckPrivileges.java | 23 ++--
...e.doris.mysql.privilege.AccessControllerFactory | 18 +++
16 files changed, 594 insertions(+), 111 deletions(-)
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index f884c9e3d85..879e48b204c 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -3399,4 +3399,25 @@ public class Config extends ConfigBase {
@ConfField(mutable = true, description = {"Prometheus 输出表维度指标的个数限制",
"Prometheus output table dimension metric count limit"})
public static int prom_output_table_metrics_limit = 10000;
+
+ @ConfField(description = {"认证插件目录",
+ "Authentication plugin directory"})
+ public static String authentication_plugins_dir = EnvUtils.getDorisHome()
+ "/plugins/authentication";
+
+ @ConfField(description = {"鉴权插件目录",
+ "Authorization plugin directory"})
+ public static String authorization_plugins_dir = EnvUtils.getDorisHome() +
"/plugins/authorization";
+
+ @ConfField(description = {
+ "鉴权插件配置文件路径,需在 DORIS_HOME 下,默认为 conf/authorization.conf",
+ "Authorization plugin configuration file path, need to be in
DORIS_HOME,"
+ + "default is conf/authorization.conf"})
+ public static String authorization_config_file_path =
"/conf/authorization.conf";
+
+ @ConfField(description = {
+ "认证插件配置文件路径,需在 DORIS_HOME 下,默认为 conf/authentication.conf",
+ "Authentication plugin configuration file path, need to be in
DORIS_HOME,"
+ + "default is conf/authentication.conf"})
+ public static String authentication_config_file_path =
"/conf/authentication.conf";
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
index 359910aba49..9ed9daa08f8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
@@ -46,36 +46,21 @@ public class RangerDorisAccessController extends
RangerAccessController {
private static final String GLOBAL_PRIV_FIXED_NAME = "*";
private RangerBasePlugin dorisPlugin;
-
- private static RangerDorisAccessController instance;
-
// private static ScheduledThreadPoolExecutor logFlushTimer =
ThreadPoolManager.newDaemonScheduledThreadPool(1,
// "ranger-doris-audit-log-flusher-timer", true);
// private RangerHiveAuditHandler auditHandler;
- private RangerDorisAccessController(String serviceName) {
+ public RangerDorisAccessController(String serviceName) {
this(serviceName, null);
}
- private RangerDorisAccessController(String serviceName,
RangerAuthContextListener rangerAuthContextListener) {
+ public RangerDorisAccessController(String serviceName,
RangerAuthContextListener rangerAuthContextListener) {
dorisPlugin = new RangerDorisPlugin(serviceName,
rangerAuthContextListener);
// auditHandler = new RangerHiveAuditHandler(dorisPlugin.getConfig());
// start a timed log flusher
// logFlushTimer.scheduleAtFixedRate(new
RangerHiveAuditLogFlusher(auditHandler), 10, 20L, TimeUnit.SECONDS);
}
- public static RangerDorisAccessController getInstance(String serviceName) {
- return getInstance(serviceName, null);
- }
-
- public static synchronized RangerDorisAccessController getInstance(String
serviceName,
- RangerAuthContextListener rangerAuthContextListener) {
- if (instance == null) {
- instance = new RangerDorisAccessController(serviceName,
rangerAuthContextListener);
- }
- return instance;
- }
-
@VisibleForTesting
public RangerDorisAccessController(RangerBasePlugin plugin) {
dorisPlugin = plugin;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
index 3e9f11d9f8e..a45632ff9e6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
@@ -23,6 +23,12 @@ import
org.apache.doris.mysql.privilege.CatalogAccessController;
import java.util.Map;
public class RangerHiveAccessControllerFactory implements
AccessControllerFactory {
+
+ @Override
+ public String factoryIdentifier() {
+ return "ranger-hive";
+ }
+
@Override
public CatalogAccessController createAccessController(Map<String, String>
prop) {
return new RangerHiveAccessController(prop);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/util/ChildFirstClassLoader.java
b/fe/fe-core/src/main/java/org/apache/doris/common/util/ChildFirstClassLoader.java
new file mode 100644
index 00000000000..ad3b0dfe77d
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/util/ChildFirstClassLoader.java
@@ -0,0 +1,151 @@
+// 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.doris.common.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * ChildFirstClassLoader is a custom class loader designed to load classes from
+ * plugin JAR files. It uses a child-first class loading strategy, where the
loader
+ * first attempts to load classes from its own URLs (plugin JARs), and if the
class
+ * is not found, it delegates the loading to its parent class loader.
+ * <p>
+ * This class is intended for plugin-based systems where classes defined in
plugins
+ * might override or replace standard library classes.
+ * <p>
+ * Key features:
+ * - Child-First loading mechanism.
+ * - Support for loading classes from multiple JAR files.
+ * - Efficient caching of JAR file resources to avoid repeated file access.
+ */
+public class ChildFirstClassLoader extends URLClassLoader {
+
+ // A list of URLs pointing to JAR files
+ private final List<URL> jarURLs;
+
+ /**
+ * Constructs a new ChildFirstClassLoader with the given URLs and parent
class loader.
+ * This constructor stores the URLs for class loading.
+ *
+ * @param urls The URLs pointing to the plugin JAR files.
+ * @param parent The parent class loader to use for delegation if class is
not found.
+ * @throws IOException If there is an error opening the JAR files.
+ * @throws URISyntaxException If there is an error converting the URL to
URI.
+ */
+ public ChildFirstClassLoader(URL[] urls, ClassLoader parent) throws
IOException, URISyntaxException {
+ super(urls, parent);
+ this.jarURLs = new ArrayList<>();
+ for (URL url : urls) {
+ if ("file".equals(url.getProtocol())) {
+ this.jarURLs.add(url);
+ }
+ }
+ }
+
+ /**
+ * Attempts to load the class with the specified name.
+ * This method first tries to find the class using the current class
loader (child-first strategy),
+ * and if the class is not found, it delegates the loading to the parent
class loader.
+ *
+ * @param name The fully qualified name of the class to be loaded.
+ * @param resolve If true, the class will be resolved after being loaded.
+ * @return The resulting Class object.
+ * @throws ClassNotFoundException If the class cannot be found by either
the child or parent loader.
+ */
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException {
+ // Child-First mechanism: try to find the class locally first
+ try {
+ return findClass(name);
+ } catch (ClassNotFoundException e) {
+ // If the class is not found locally, delegate to the parent class
loader
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ /**
+ * Searches for the class in the loaded plugin JAR files.
+ * If the class is found in one of the JAR files, it will be defined and
returned.
+ *
+ * @param name The fully qualified name of the class to find.
+ * @return The resulting Class object.
+ * @throws ClassNotFoundException If the class cannot be found in the JAR
files.
+ */
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ String classFile = name.replace('.', '/') + ".class"; // Convert
class name to path
+
+ // Iterate over all the JAR URLs to find the class
+ for (URL jarURL : jarURLs) {
+ try (JarFile jarFile = new
JarFile(Paths.get(jarURL.toURI()).toFile())) {
+ JarEntry entry = jarFile.getJarEntry(classFile);
+ if (entry != null) {
+ try (InputStream inputStream =
jarFile.getInputStream(entry)) {
+ byte[] classData = readAllBytes(inputStream);
+ // Define the class from the byte array
+ return defineClass(name, classData, 0,
classData.length);
+ }
+ }
+ } catch (IOException | URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ // If the class was not found in any JAR file, throw
ClassNotFoundException
+ throw new ClassNotFoundException(name);
+ }
+
+ /**
+ * Reads all bytes from the given InputStream.
+ * This method reads the entire content of the InputStream and returns it
as a byte array.
+ *
+ * @param inputStream The InputStream to read from.
+ * @return A byte array containing the data from the InputStream.
+ * @throws IOException If an I/O error occurs while reading the stream.
+ */
+ private byte[] readAllBytes(InputStream inputStream) throws IOException {
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream())
{
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ return outputStream.toByteArray();
+ }
+ }
+
+ /**
+ * Closes all open JAR files and releases any resources held by this class
loader.
+ * This method should be called when the class loader is no longer needed
to avoid resource leaks.
+ *
+ * @throws IOException If an I/O error occurs while closing the JAR files.
+ */
+ @Override
+ public void close() throws IOException {
+ super.close(); // Call the superclass close method
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
new file mode 100644
index 00000000000..c82858c7d01
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
@@ -0,0 +1,126 @@
+// 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.doris.common.util;
+
+import org.apache.doris.common.Config;
+import org.apache.doris.mysql.authenticate.AuthenticatorFactory;
+import org.apache.doris.mysql.privilege.AccessControllerFactory;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * Utility class for loading service implementations from external JAR files
in specific plugin directories.
+ * <p>
+ * This class provides a mechanism to dynamically load service implementations
from JAR files located in
+ * plugin directories, which are mapped by the service type's simple name. It
uses a child-first class loading
+ * strategy to ensure that plugins in the JAR files are prioritized over
classes loaded by the parent class loader.
+ * <p>
+ * It is particularly useful in scenarios where the system needs to support
modular or pluggable architectures,
+ * such as dynamically loading authenticators, access controllers, or other
pluggable services from external
+ * directories without requiring the services to be bundled with the core
application.
+ * <p>
+ * Plugin directory mappings are maintained in a static map where the key is
the simple name of the service class,
+ * and the value is the relative path to the directory containing the plugin
JARs.
+ * <p>
+ * Example usage:
+ * <pre>
+ * {@code
+ * List<AuthenticatorFactory> authenticators =
ClassLoaderUtils.loadServicesFromDirectory(AuthenticatorFactory.class);
+ * }
+ * </pre>
+ *
+ * @see ServiceLoader
+ * @see ChildFirstClassLoader
+ */
+public class ClassLoaderUtils {
+ private static final Logger LOG =
LogManager.getLogger(ClassLoaderUtils.class);
+ // A mapping of service class simple names to their respective plugin
directories.
+ private static final Map<String, String> pluginDirMapping = new
HashedMap();
+
+ static {
+ pluginDirMapping.put(AuthenticatorFactory.class.getSimpleName(),
Config.authentication_plugins_dir);
+ pluginDirMapping.put(AccessControllerFactory.class.getSimpleName(),
Config.authorization_plugins_dir);
+ }
+
+ /**
+ * Loads service implementations from JAR files in the specified plugin
directory.
+ * <p>
+ * The method first looks up the directory for the given service class
type from the {@code pluginDirMapping}.
+ * If a directory exists and contains JAR files, it will load the service
implementations from those JAR files
+ * using a child-first class loader to prioritize the plugin classes.
+ * <p>
+ * If no directory is found for the service type, or the directory is
invalid, an exception is thrown. If the
+ * directory does not contain any JAR files, an empty list is returned.
+ *
+ * @param serviceClass The class type of the service to load. This should
be the interface or
+ * base class of the service.
+ * @param <T> The type of the service.
+ * @return A list of service instances loaded from JAR files. If no
services are found, an empty list is returned.
+ * @throws IOException If there is an error reading the JAR files or
the directory is invalid.
+ * @throws RuntimeException If there is a problem with the directory
mapping or JAR file URL creation.
+ */
+ public static <T> List<T> loadServicesFromDirectory(Class<T> serviceClass)
throws IOException {
+ String pluginDirKey = serviceClass.getSimpleName();
+ String pluginDir = pluginDirMapping.get(pluginDirKey);
+ if (pluginDir == null) {
+ throw new RuntimeException("No mapping found for plugin directory
key: " + pluginDirKey);
+ }
+ File jarDir = new File(pluginDir);
+ // If the directory does not exist, return an empty list.
+ if (!jarDir.exists()) {
+ return new ArrayList<>();
+ }
+ if (!jarDir.isDirectory()) {
+ throw new IOException("The specified path is not a directory: " +
pluginDir);
+ }
+
+ File[] jarFiles = jarDir.listFiles((dir, name) ->
name.endsWith(".jar"));
+ if (jarFiles == null || jarFiles.length == 0) {
+ LOG.info("No JAR files found in the plugin directory: {}",
pluginDir);
+ return new ArrayList<>();
+ }
+
+ List<T> services = new ArrayList<>();
+ for (File jarFile : jarFiles) {
+ URL[] jarURLs;
+ jarURLs = new URL[]{jarFile.toURI().toURL()};
+
+ try (ChildFirstClassLoader urlClassLoader = new
ChildFirstClassLoader(jarURLs,
+ Thread.currentThread().getContextClassLoader())) {
+ ServiceLoader<T> serviceLoader =
ServiceLoader.load(serviceClass, urlClassLoader);
+ for (T service : serviceLoader) {
+ services.add(service);
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return services;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
b/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
index dbe9eb0d41d..816091df51b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
@@ -37,7 +37,6 @@ import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.Pair;
import org.apache.doris.datasource.CatalogIf;
-import org.apache.doris.datasource.CatalogMgr;
import org.apache.doris.datasource.ExternalCatalog;
import org.apache.doris.nereids.types.DataType;
import org.apache.doris.policy.Policy;
@@ -1679,16 +1678,6 @@ public class PropertyAnalyzer {
// "access_controller.properties.prop2" = "yyy",
// )
// 1. get access controller class
- String acClass =
properties.getOrDefault(CatalogMgr.ACCESS_CONTROLLER_CLASS_PROP, "");
- if (!Strings.isNullOrEmpty(acClass)) {
- // 2. check if class exists
- try {
- Class.forName(acClass);
- } catch (ClassNotFoundException e) {
- throw new AnalysisException("failed to find class " + acClass,
e);
- }
- }
-
if (isAlter) {
// The 'use_meta_cache' property can not be modified
if (properties.containsKey(ExternalCatalog.USE_META_CACHE)) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
index 343134fb8fd..8ba711e6655 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
@@ -17,22 +17,21 @@
package org.apache.doris.mysql.authenticate;
-import org.apache.doris.common.EnvUtils;
+import org.apache.doris.common.util.ClassLoaderUtils;
import org.apache.doris.mysql.MysqlAuthPacket;
import org.apache.doris.mysql.MysqlChannel;
import org.apache.doris.mysql.MysqlHandshakePacket;
import org.apache.doris.mysql.MysqlProto;
import org.apache.doris.mysql.MysqlSerializer;
import org.apache.doris.mysql.authenticate.password.Password;
+import org.apache.doris.plugin.PropertiesUtils;
import org.apache.doris.qe.ConnectContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.ServiceLoader;
@@ -66,9 +65,28 @@ public class AuthenticatorManager {
for (AuthenticatorFactory factory : loader) {
LOG.info("Found Authenticator Plugin Factory: {}",
factory.factoryIdentifier());
if (factory.factoryIdentifier().equalsIgnoreCase(identifier)) {
- return factory.create(loadConfigFile());
+ Properties properties =
PropertiesUtils.loadAuthenticationConfigFile();
+ return factory.create(properties);
}
}
+ return loadCustomerFactories(identifier);
+
+ }
+
+ private Authenticator loadCustomerFactories(String identifier) throws
Exception {
+ List<AuthenticatorFactory> factories =
ClassLoaderUtils.loadServicesFromDirectory(AuthenticatorFactory.class);
+ if (factories.isEmpty()) {
+ LOG.info("No customer authenticator found, using default
authenticator");
+ return defaultAuthenticator;
+ }
+ for (AuthenticatorFactory factory : factories) {
+ LOG.info("Found Customer Authenticator Plugin Factory: {}",
factory.factoryIdentifier());
+ if (factory.factoryIdentifier().equalsIgnoreCase(identifier)) {
+ Properties properties =
PropertiesUtils.loadAuthenticationConfigFile();
+ return factory.create(properties);
+ }
+ }
+
throw new RuntimeException("No AuthenticatorFactory found for
identifier: " + identifier);
}
@@ -100,15 +118,4 @@ public class AuthenticatorManager {
private Authenticator chooseAuthenticator(String userName) {
return authTypeAuthenticator.canDeal(userName) ? authTypeAuthenticator
: defaultAuthenticator;
}
-
- private static Properties loadConfigFile() throws Exception {
- String configFilePath = EnvUtils.getDorisHome() +
"/conf/authenticate.conf";
- if (new File(configFilePath).exists()) {
- LOG.info("Loading authenticate configuration file: {}",
configFilePath);
- Properties properties = new Properties();
- properties.load(Files.newInputStream(Paths.get(configFilePath)));
- return properties;
- }
- return new Properties();
- }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
index d4e0400c9eb..8d1481aa070 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
@@ -20,6 +20,14 @@ package org.apache.doris.mysql.privilege;
import java.util.Map;
public interface AccessControllerFactory {
+ /**
+ * Returns the identifier for the factory, such as "range-doris".
+ *
+ * @return the factory identifier
+ */
+ default String factoryIdentifier() {
+ return this.getClass().getSimpleName();
+ }
CatalogAccessController createAccessController(Map<String, String> prop);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
index 86aad9af71e..91f8e415cdb 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
@@ -22,12 +22,13 @@ import org.apache.doris.analysis.TableName;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.AuthorizationInfo;
import org.apache.doris.catalog.Env;
-import
org.apache.doris.catalog.authorizer.ranger.doris.RangerDorisAccessController;
import org.apache.doris.common.Config;
import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.ClassLoaderUtils;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.datasource.ExternalCatalog;
import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.plugin.PropertiesUtils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.base.Preconditions;
@@ -36,11 +37,14 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.ServiceLoader;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* AccessControllerManager is the entry point of privilege authentication.
@@ -53,19 +57,62 @@ public class AccessControllerManager {
private static final Logger LOG =
LogManager.getLogger(AccessControllerManager.class);
private Auth auth;
+ // Default access controller instance used for handling cases where no
specific controller is specified
private CatalogAccessController defaultAccessController;
+ // Map that stores the mapping between catalogs and their corresponding
access controllers
private Map<String, CatalogAccessController> ctlToCtlAccessController =
Maps.newConcurrentMap();
+ // Cache of loaded access controller factories for quick creation of new
access controllers
+ private ConcurrentHashMap<String, AccessControllerFactory>
accessControllerFactoriesCache
+ = new ConcurrentHashMap<>();
+ // Mapping between access controller class names and their identifiers for
easy lookup of factory identifiers
+ private ConcurrentHashMap<String, String> accessControllerClassNameMapping
= new ConcurrentHashMap<>();
public AccessControllerManager(Auth auth) {
this.auth = auth;
- if (Config.access_controller_type.equalsIgnoreCase("ranger-doris")) {
- defaultAccessController =
RangerDorisAccessController.getInstance("doris");
- } else {
- defaultAccessController = new InternalAccessController(auth);
- }
+ loadAccessControllerPlugins();
+ String accessControllerName = Config.access_controller_type;
+ this.defaultAccessController =
loadAccessControllerOrThrow(accessControllerName);
ctlToCtlAccessController.put(InternalCatalog.INTERNAL_CATALOG_NAME,
defaultAccessController);
}
+ private CatalogAccessController loadAccessControllerOrThrow(String
accessControllerName) {
+ if (accessControllerName.equalsIgnoreCase("default")) {
+ return new InternalAccessController(auth);
+ }
+ if (accessControllerFactoriesCache.containsKey(accessControllerName)) {
+ Map<String, String> prop;
+ try {
+ prop = PropertiesUtils.loadAccessControllerPropertiesOrNull();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to load authorization
properties."
+ + "Please check the configuration file, authorization
name is " + accessControllerName, e);
+ }
+ return
accessControllerFactoriesCache.get(accessControllerName).createAccessController(prop);
+ }
+ throw new RuntimeException("No authorization plugin factory found for
" + accessControllerName
+ + ". Please confirm that your plugin is placed in the correct
location.");
+ }
+
+ private void loadAccessControllerPlugins() {
+ ServiceLoader<AccessControllerFactory> loaderFromClasspath =
ServiceLoader.load(AccessControllerFactory.class);
+ for (AccessControllerFactory factory : loaderFromClasspath) {
+ LOG.info("Found Authentication Plugin Factories: {} from class
path.", factory.factoryIdentifier());
+ accessControllerFactoriesCache.put(factory.factoryIdentifier(),
factory);
+ accessControllerClassNameMapping.put(factory.getClass().getName(),
factory.factoryIdentifier());
+ }
+ List<AccessControllerFactory> loader = null;
+ try {
+ loader =
ClassLoaderUtils.loadServicesFromDirectory(AccessControllerFactory.class);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to load Authentication Plugin
Factories", e);
+ }
+ for (AccessControllerFactory factory : loader) {
+ LOG.info("Found Access Controller Plugin Factory: {} from
directory.", factory.factoryIdentifier());
+ accessControllerFactoriesCache.put(factory.factoryIdentifier(),
factory);
+ accessControllerClassNameMapping.put(factory.getClass().getName(),
factory.factoryIdentifier());
+ }
+ }
+
public CatalogAccessController getAccessControllerOrDefault(String ctl) {
CatalogAccessController catalogAccessController =
ctlToCtlAccessController.get(ctl);
if (catalogAccessController != null) {
@@ -95,23 +142,28 @@ public class AccessControllerManager {
}
public void createAccessController(String ctl, String acFactoryClassName,
Map<String, String> prop,
- boolean isDryRun) {
- Class<?> factoryClazz = null;
- try {
- factoryClazz = Class.forName(acFactoryClassName);
- AccessControllerFactory factory = (AccessControllerFactory)
factoryClazz.newInstance();
- CatalogAccessController accessController =
factory.createAccessController(prop);
- if (!isDryRun) {
- ctlToCtlAccessController.put(ctl, accessController);
- LOG.info("create access controller {} for catalog {}", ctl,
acFactoryClassName);
- }
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
- } catch (InstantiationException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
+ boolean isDryRun) {
+ String pluginIdentifier =
getPluginIdentifierForAccessController(acFactoryClassName);
+ CatalogAccessController accessController =
accessControllerFactoriesCache.get(pluginIdentifier)
+ .createAccessController(prop);
+ if (!isDryRun) {
+ ctlToCtlAccessController.put(ctl, accessController);
+ LOG.info("create access controller {} for catalog {}",
acFactoryClassName, ctl);
+ }
+ }
+
+ private String getPluginIdentifierForAccessController(String acClassName) {
+ String pluginIdentifier = null;
+ if (accessControllerClassNameMapping.containsKey(acClassName)) {
+ pluginIdentifier =
accessControllerClassNameMapping.get(acClassName);
+ }
+ if (accessControllerFactoriesCache.containsKey(acClassName)) {
+ pluginIdentifier = acClassName;
+ }
+ if (null == pluginIdentifier ||
!accessControllerFactoriesCache.containsKey(pluginIdentifier)) {
+ throw new RuntimeException("Access Controller Plugin Factory not
found for " + acClassName);
}
+ return pluginIdentifier;
}
public void removeAccessController(String ctl) {
@@ -166,7 +218,7 @@ public class AccessControllerManager {
}
public boolean checkTblPriv(ConnectContext ctx, String qualifiedCtl,
- String qualifiedDb, String tbl, PrivPredicate wanted) {
+ String qualifiedDb, String tbl, PrivPredicate
wanted) {
if (ctx.isSkipAuth()) {
return true;
}
@@ -181,7 +233,7 @@ public class AccessControllerManager {
// ==== Column ====
// If param has ctx, we can skip auth by isSkipAuth field in ctx
public void checkColumnsPriv(ConnectContext ctx, String ctl, String
qualifiedDb, String tbl, Set<String> cols,
- PrivPredicate wanted) throws UserException {
+ PrivPredicate wanted) throws UserException {
if (ctx.isSkipAuth()) {
return;
}
@@ -190,7 +242,7 @@ public class AccessControllerManager {
public void checkColumnsPriv(UserIdentity currentUser, String
ctl, String qualifiedDb, String tbl, Set<String> cols,
- PrivPredicate wanted) throws UserException {
+ PrivPredicate wanted) throws UserException {
boolean hasGlobal = checkGlobalPriv(currentUser, wanted);
CatalogAccessController accessController =
getAccessControllerOrDefault(ctl);
long start = System.currentTimeMillis();
@@ -217,7 +269,7 @@ public class AccessControllerManager {
}
public boolean checkCloudPriv(UserIdentity currentUser, String cloudName,
- PrivPredicate wanted, ResourceTypeEnum type) {
+ PrivPredicate wanted, ResourceTypeEnum type)
{
return defaultAccessController.checkCloudPriv(currentUser, cloudName,
wanted, type);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDorisAccessControllerFactory.java
similarity index 67%
copy from
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
copy to
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDorisAccessControllerFactory.java
index d4e0400c9eb..28093ad7886 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerFactory.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDorisAccessControllerFactory.java
@@ -17,9 +17,18 @@
package org.apache.doris.mysql.privilege;
+import
org.apache.doris.catalog.authorizer.ranger.doris.RangerDorisAccessController;
+
import java.util.Map;
-public interface AccessControllerFactory {
+public class RangerDorisAccessControllerFactory implements
AccessControllerFactory {
+ @Override
+ public String factoryIdentifier() {
+ return "ranger-doris";
+ }
- CatalogAccessController createAccessController(Map<String, String> prop);
+ @Override
+ public RangerDorisAccessController createAccessController(Map<String,
String> prop) {
+ return new RangerDorisAccessController("doris");
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
index 816ce769a31..29ae1f438a1 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
@@ -29,6 +29,7 @@ import org.apache.doris.common.UserException;
import org.apache.doris.common.io.Text;
import org.apache.doris.common.io.Writable;
import org.apache.doris.load.DppConfig;
+import org.apache.doris.mysql.authenticate.AuthenticateType;
import org.apache.doris.persist.gson.GsonUtils;
import org.apache.doris.resource.Tag;
@@ -55,9 +56,9 @@ public class UserPropertyMgr implements Writable {
protected Map<String, UserProperty> propertyMap = Maps.newHashMap();
public static final String ROOT_USER = "root";
public static final String SYSTEM_RESOURCE_USER = "system";
- public static final String LDAP_RESOURCE_USER = "ldap";
-
- private static final UserProperty LDAP_PROPERTY = new
UserProperty(LDAP_RESOURCE_USER);
+ public static final String DEFAULT_RESOURCE_USER =
Config.authentication_type;
+ // When using a non-internal authentication plugin, the user property
information uses the default configuration.
+ private static final UserProperty DEFAULT_USER_PROPERTY = new
UserProperty(DEFAULT_RESOURCE_USER);
@SerializedName(value = "resourceVersion")
private AtomicLong resourceVersion = new AtomicLong(0);
@@ -93,7 +94,7 @@ public class UserPropertyMgr implements Writable {
public int getQueryTimeout(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return 0;
}
@@ -102,7 +103,7 @@ public class UserPropertyMgr implements Writable {
public int getInsertTimeout(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return 0;
}
@@ -111,7 +112,7 @@ public class UserPropertyMgr implements Writable {
public long getMaxConn(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return 0;
}
@@ -120,7 +121,7 @@ public class UserPropertyMgr implements Writable {
public long getMaxQueryInstances(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return Config.default_max_query_instances;
}
@@ -139,21 +140,21 @@ public class UserPropertyMgr implements Writable {
List<String> ret = new ArrayList<>();
users.forEach(
u -> {
- UserProperty userProperty = propertyMap.get(u);
- if (userProperty == null) {
- return;
- }
- if (clusterName.equals(userProperty.getDefaultCloudCluster()))
{
- ret.add(ClusterNamespace.getNameFromFullName(u));
+ UserProperty userProperty = propertyMap.get(u);
+ if (userProperty == null) {
+ return;
+ }
+ if
(clusterName.equals(userProperty.getDefaultCloudCluster())) {
+ ret.add(ClusterNamespace.getNameFromFullName(u));
+ }
}
- }
);
return ret;
}
public int getParallelFragmentExecInstanceNum(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return -1;
}
@@ -162,7 +163,7 @@ public class UserPropertyMgr implements Writable {
public Set<Tag> getResourceTags(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return UserProperty.INVALID_RESOURCE_TAGS;
}
@@ -182,7 +183,7 @@ public class UserPropertyMgr implements Writable {
Pair<String, DppConfig> loadClusterInfo = null;
UserProperty property = propertyMap.get(qualifiedUser);
- property = getLdapPropertyIfNull(qualifiedUser, property);
+ property = getPropertyIfNull(qualifiedUser, property);
if (property == null) {
throw new DdlException("User " + qualifiedUser + " does not
exist");
}
@@ -192,7 +193,7 @@ public class UserPropertyMgr implements Writable {
public List<List<String>> fetchUserProperty(String qualifiedUser) throws
AnalysisException {
UserProperty property = propertyMap.get(qualifiedUser);
- property = getLdapPropertyIfNull(qualifiedUser, property);
+ property = getPropertyIfNull(qualifiedUser, property);
if (property == null) {
throw new AnalysisException("User " + qualifiedUser + " does not
exist");
}
@@ -201,16 +202,16 @@ public class UserPropertyMgr implements Writable {
public String[] getSqlBlockRules(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
- return new String[] {};
+ return new String[]{};
}
return existProperty.getSqlBlockRules();
}
public int getCpuResourceLimit(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return -1;
}
@@ -219,7 +220,7 @@ public class UserPropertyMgr implements Writable {
public long getExecMemLimit(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return -1;
}
@@ -228,7 +229,7 @@ public class UserPropertyMgr implements Writable {
public String getWorkloadGroup(String qualifiedUser) {
UserProperty existProperty = propertyMap.get(qualifiedUser);
- existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
+ existProperty = getPropertyIfNull(qualifiedUser, existProperty);
if (existProperty == null) {
return null;
}
@@ -244,9 +245,24 @@ public class UserPropertyMgr implements Writable {
return Pair.of(false, "");
}
- private UserProperty getLdapPropertyIfNull(String qualifiedUser,
UserProperty existProperty) {
- if (existProperty == null &&
Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser)) {
- return LDAP_PROPERTY;
+ /**
+ * The method determines which user property to return based on the
existProperty parameter
+ * and system configuration:
+ * If existProperty is not null, return it directly.
+ * If the authentication type is LDAP and the user exists in LDAP, return
DEFAULT_USER_PROPERTY.
+ * If the authentication type is not the default type, return
DEFAULT_USER_PROPERTY.
+ * Otherwise, return existProperty.
+ */
+ private UserProperty getPropertyIfNull(String qualifiedUser, UserProperty
existProperty) {
+ if (null != existProperty) {
+ return existProperty;
+ }
+ if
(AuthenticateType.LDAP.name().equalsIgnoreCase(Config.authentication_type)
+ &&
Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser)) {
+ return DEFAULT_USER_PROPERTY;
+ }
+ if
(!Config.authentication_type.equalsIgnoreCase(AuthenticateType.DEFAULT.name()))
{
+ return DEFAULT_USER_PROPERTY;
}
return existProperty;
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/plugin/PropertiesUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/plugin/PropertiesUtils.java
new file mode 100644
index 00000000000..7318be7da61
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/plugin/PropertiesUtils.java
@@ -0,0 +1,68 @@
+// 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.doris.plugin;
+
+import org.apache.doris.common.Config;
+import org.apache.doris.common.EnvUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class PropertiesUtils {
+
+ public static final Logger LOG =
LogManager.getLogger(PropertiesUtils.class);
+
+ public static Map<String, String> loadAccessControllerPropertiesOrNull()
throws IOException {
+ String configFilePath = EnvUtils.getDorisHome() +
Config.authorization_config_file_path;
+ if (new File(configFilePath).exists()) {
+ Properties properties = new Properties();
+ properties.load(Files.newInputStream(Paths.get(configFilePath)));
+ return propertiesToMap(properties);
+ }
+ return null;
+ }
+
+ public static Properties loadAuthenticationConfigFile() throws Exception {
+ String configFilePath = EnvUtils.getDorisHome() +
Config.authentication_config_file_path;
+ if (new File(configFilePath).exists()) {
+ LOG.info("Loading authenticate configuration file: {}",
configFilePath);
+ Properties properties = new Properties();
+ properties.load(Files.newInputStream(Paths.get(configFilePath)));
+ return properties;
+ }
+ return new Properties();
+ }
+
+ public static Map<String, String> propertiesToMap(Properties properties) {
+ Map<String, String> map = new HashMap<>();
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+ String key = String.valueOf(entry.getKey());
+ String value = String.valueOf(entry.getValue());
+ map.put(key, value);
+ }
+ return map;
+ }
+}
diff --git
a/fe/fe-core/src/main/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
b/fe/fe-core/src/main/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
new file mode 100644
index 00000000000..e2100cb8b23
--- /dev/null
+++
b/fe/fe-core/src/main/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+org.apache.doris.mysql.privilege.RangerDorisAccessControllerFactory
+org.apache.doris.catalog.authorizer.ranger.hive.RangerHiveAccessControllerFactory
\ No newline at end of file
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/CustomAccessControllerFactory.java
similarity index 78%
copy from
fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
copy to
fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/CustomAccessControllerFactory.java
index 3e9f11d9f8e..f30ab8def4f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerFactory.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/CustomAccessControllerFactory.java
@@ -15,16 +15,21 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.catalog.authorizer.ranger.hive;
+package org.apache.doris.nereids.privileges;
import org.apache.doris.mysql.privilege.AccessControllerFactory;
import org.apache.doris.mysql.privilege.CatalogAccessController;
import java.util.Map;
-public class RangerHiveAccessControllerFactory implements
AccessControllerFactory {
+public class CustomAccessControllerFactory implements AccessControllerFactory {
+ @Override
+ public String factoryIdentifier() {
+ return "CustomAccess";
+ }
+
@Override
public CatalogAccessController createAccessController(Map<String, String>
prop) {
- return new RangerHiveAccessController(prop);
+ return new TestCheckPrivileges.SimpleCatalogAccessController();
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
index dafc33a64af..89220e7dffd 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java
@@ -23,10 +23,10 @@ import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.common.AuthorizationException;
+import org.apache.doris.common.DdlException;
import org.apache.doris.common.FeConstants;
import org.apache.doris.datasource.CatalogMgr;
import
org.apache.doris.datasource.test.TestExternalCatalog.TestCatalogProvider;
-import org.apache.doris.mysql.privilege.AccessControllerFactory;
import org.apache.doris.mysql.privilege.AccessControllerManager;
import org.apache.doris.mysql.privilege.CatalogAccessController;
import org.apache.doris.mysql.privilege.DataMaskPolicy;
@@ -92,10 +92,20 @@ public class TestCheckPrivileges extends TestWithFeService
implements GeneratedM
String catalogProvider
=
"org.apache.doris.nereids.privileges.TestCheckPrivileges$CustomCatalogProvider";
String accessControllerFactory
- =
"org.apache.doris.nereids.privileges.TestCheckPrivileges$CustomAccessControllerFactory";
-
+ =
"org.apache.doris.nereids.privileges.CustomAccessControllerFactory";
String catalog = "custom_catalog";
String db = "test_db";
+ String failedAccessControllerFactory
+ =
"org.apache.doris.nereids.privileges.FailedAccessControllerFactory";
+ //try to create catalog with failed access controller
+ Assertions.assertThrows(DdlException.class, () -> {
+ createCatalog("create catalog " + catalog + " properties("
+ + " \"type\"=\"test\","
+ + " \"catalog_provider.class\"=\"" + catalogProvider +
"\","
+ + " \"" + CatalogMgr.ACCESS_CONTROLLER_CLASS_PROP +
"\"=\"" + failedAccessControllerFactory + "\""
+ + ")");
+ }, "Failed to init access controller");
+
createCatalog("create catalog " + catalog + " properties("
+ " \"type\"=\"test\","
+ " \"catalog_provider.class\"=\"" + catalogProvider + "\","
@@ -314,13 +324,6 @@ public class TestCheckPrivileges extends TestWithFeService
implements GeneratedM
}
}
- public static class CustomAccessControllerFactory implements
AccessControllerFactory {
- @Override
- public CatalogAccessController createAccessController(Map<String,
String> prop) {
- return new SimpleCatalogAccessController();
- }
- }
-
public static class SimpleCatalogAccessController implements
CatalogAccessController {
private static ThreadLocal<List<TablePrivilege>> tablePrivileges = new
ThreadLocal<>();
private static ThreadLocal<List<ColumnPrivilege>> columnPrivileges =
new ThreadLocal<>();
diff --git
a/fe/fe-core/src/test/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
b/fe/fe-core/src/test/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
new file mode 100644
index 00000000000..83924e7e0f6
--- /dev/null
+++
b/fe/fe-core/src/test/resources/META-INF/services/org.apache.doris.mysql.privilege.AccessControllerFactory
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+#
+org.apache.doris.nereids.privileges.CustomAccessControllerFactory
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]