This is an automated email from the ASF dual-hosted git repository.
kirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new b263bb8a504 [feat](authentication)Support AuthenticationIntegration
DDL (#60902)
b263bb8a504 is described below
commit b263bb8a504726ad99eef95657cc4f63a3c13df0
Author: Calvin Kirs <[email protected]>
AuthorDate: Thu Mar 5 14:10:38 2026 +0800
[feat](authentication)Support AuthenticationIntegration DDL (#60902)
This PR adds DDL support for managing AUTHENTICATION INTEGRATION objects
in Doris FE.
https://github.com/apache/doris/issues/60361
It introduces:
1. CREATE / ALTER / DROP AUTHENTICATION INTEGRATION syntax.
2. Validation rules:
- type is required on CREATE.
- type cannot be changed in ALTER.
3. ADMIN privilege checks for these operations.
4. Metadata persistence and replay support (edit log + image load/save).
5. Unit tests and regression tests for parser, command behavior, and
persistence.
```
CREATE AUTHENTICATION INTEGRATION <integration_name>
WITH PROPERTIES (
'type' = '<type>'
[, '<property_key>' = '<property_value>' ...]
)
[COMMENT '<comment>'];
ALTER AUTHENTICATION INTEGRATION <integration_name>
SET PROPERTIES (
'<property_key>' = '<property_value>'
[, '<property_key>' = '<property_value>' ...]
);
ALTER AUTHENTICATION INTEGRATION <integration_name>
SET COMMENT '<comment>';
DROP AUTHENTICATION INTEGRATION [IF EXISTS] <integration_name>;
-- Optional cleanup
DROP AUTHENTICATION INTEGRATION IF EXISTS corp_ldap;
-- Create an authentication integration
CREATE AUTHENTICATION INTEGRATION corp_ldap
WITH PROPERTIES (
'type'='ldap',
'ldap.server'='ldap://127.0.0.1:389',
'ldap.base_dn'='dc=example,dc=com',
'ldap.admin_dn'='cn=admin,dc=example,dc=com',
'ldap.admin_password'='123456'
)
COMMENT 'Corporate LDAP integration';
-- Alter properties (type cannot be changed)
ALTER AUTHENTICATION INTEGRATION corp_ldap
SET PROPERTIES (
'ldap.server'='ldap://127.0.0.1:1389',
'ldap.admin_password'='abcdef'
);
-- Alter comment
ALTER AUTHENTICATION INTEGRATION corp_ldap
SET COMMENT 'Updated LDAP integration config';
-- Drop integration
DROP AUTHENTICATION INTEGRATION corp_ldap;
-- Drop safely if not exists
DROP AUTHENTICATION INTEGRATION IF EXISTS corp_ldap_not_exist;
-- Negative case: missing required 'type' (should fail)
CREATE AUTHENTICATION INTEGRATION bad_case
WITH PROPERTIES (
'ldap.server'='ldap://127.0.0.1:389'
);
-- Negative case: modifying 'type' in ALTER (should fail)
ALTER AUTHENTICATION INTEGRATION corp_ldap
SET PROPERTIES (
'type'='oidc'
);
```
---
.../antlr4/org/apache/doris/nereids/DorisLexer.g4 | 2 +
.../antlr4/org/apache/doris/nereids/DorisParser.g4 | 15 ++
.../AuthenticationIntegrationMeta.java | 162 ++++++++++++++
.../AuthenticationIntegrationMgr.java | 204 ++++++++++++++++++
.../main/java/org/apache/doris/catalog/Env.java | 26 +++
.../org/apache/doris/journal/JournalEntity.java | 13 ++
.../doris/nereids/parser/LogicalPlanBuilder.java | 77 +++++++
.../parser/LogicalPlanBuilderForEncryption.java | 25 +++
.../apache/doris/nereids/trees/plans/PlanType.java | 3 +
.../AlterAuthenticationIntegrationCommand.java | 135 ++++++++++++
.../CreateAuthenticationIntegrationCommand.java | 94 ++++++++
.../DropAuthenticationIntegrationCommand.java | 65 ++++++
.../trees/plans/visitor/CommandVisitor.java | 18 ++
.../DropAuthenticationIntegrationOperationLog.java | 53 +++++
.../java/org/apache/doris/persist/EditLog.java | 29 +++
.../org/apache/doris/persist/OperationType.java | 3 +
.../doris/persist/meta/MetaPersistMethod.java | 9 +
.../doris/persist/meta/PersistMetaModules.java | 8 +-
.../AuthenticationIntegrationMetaTest.java | 168 +++++++++++++++
.../AuthenticationIntegrationMgrTest.java | 206 ++++++++++++++++++
.../AuthenticationIntegrationParserTest.java | 111 ++++++++++
.../AuthenticationIntegrationCommandTest.java | 238 +++++++++++++++++++++
...pAuthenticationIntegrationOperationLogTest.java | 47 ++++
.../test_authentication_integration_auth.groovy | 78 +++++++
24 files changed, 1787 insertions(+), 2 deletions(-)
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index 0b2b5bf1ef7..7dbefd71193 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -86,6 +86,7 @@ AS: 'AS';
ASC: 'ASC';
AT: 'AT';
AUTHORS: 'AUTHORS';
+AUTHENTICATION: 'AUTHENTICATION';
AUTO: 'AUTO';
AUTO_INCREMENT: 'AUTO_INCREMENT';
ALWAYS: 'ALWAYS';
@@ -294,6 +295,7 @@ IGNORE: 'IGNORE';
IMMEDIATE: 'IMMEDIATE';
IN: 'IN';
INCREMENTAL: 'INCREMENTAL';
+INTEGRATION: 'INTEGRATION';
INDEX: 'INDEX';
INDEXES: 'INDEXES';
INFILE: 'INFILE';
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index 535fc479716..556073971a9 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -208,6 +208,8 @@ supportedCreateStatement
LIKE existedTable=multipartIdentifier
(WITH ROLLUP (rollupNames=identifierList)?)?
#createTableLike
| CREATE ROLE (IF NOT EXISTS)? name=identifierOrText (COMMENT
STRING_LITERAL)? #createRole
+ | CREATE AUTHENTICATION INTEGRATION (IF NOT EXISTS)?
integrationName=identifier
+ properties=propertyClause commentSpec?
#createAuthenticationIntegration
| CREATE WORKLOAD GROUP (IF NOT EXISTS)?
name=identifierOrText (FOR computeGroup=identifierOrText)?
properties=propertyClause? #createWorkloadGroup
| CREATE CATALOG (IF NOT EXISTS)? catalogName=identifier
@@ -292,6 +294,12 @@ supportedAlterStatement
properties=propertyClause?
#alterComputeGroup
| ALTER CATALOG name=identifier SET PROPERTIES
LEFT_PAREN propertyItemList RIGHT_PAREN
#alterCatalogProperties
+ | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+ SET properties=propertyClause
#alterAuthenticationIntegrationProperties
+ | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+ UNSET properties=propertyKeyClause
#alterAuthenticationIntegrationUnsetProperties
+ | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+ SET COMMENT comment=STRING_LITERAL
#alterAuthenticationIntegrationComment
| ALTER WORKLOAD POLICY name=identifierOrText
properties=propertyClause?
#alterWorkloadPolicy
| ALTER SQL_BLOCK_RULE name=identifier properties=propertyClause?
#alterSqlBlockRule
@@ -335,6 +343,7 @@ supportedDropStatement
| DROP STORAGE POLICY (IF EXISTS)? name=identifier
#dropStoragePolicy
| DROP WORKLOAD GROUP (IF EXISTS)? name=identifierOrText (FOR
computeGroup=identifierOrText)? #dropWorkloadGroup
| DROP CATALOG (IF EXISTS)? name=identifier
#dropCatalog
+ | DROP AUTHENTICATION INTEGRATION (IF EXISTS)? name=identifier
#dropAuthenticationIntegration
| DROP FILE name=STRING_LITERAL
((FROM | IN) database=identifier)? properties=propertyClause
#dropFile
| DROP WORKLOAD POLICY (IF EXISTS)? name=identifierOrText
#dropWorkloadPolicy
@@ -1456,6 +1465,10 @@ propertyClause
: PROPERTIES LEFT_PAREN fileProperties=propertyItemList RIGHT_PAREN
;
+propertyKeyClause
+ : PROPERTIES LEFT_PAREN keys+=propertyKey (COMMA keys+=propertyKey)*
RIGHT_PAREN
+ ;
+
propertyItemList
: properties+=propertyItem (COMMA properties+=propertyItem)*
;
@@ -1957,6 +1970,7 @@ nonReserved
| ARRAY
| AT
| AUTHORS
+ | AUTHENTICATION
| AUTO_INCREMENT
| BACKENDS
| BACKUP
@@ -2108,6 +2122,7 @@ nonReserved
| IGNORE
| IMMEDIATE
| INCREMENTAL
+ | INTEGRATION
| INDEXES
| INSERT
| INVERTED
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
new file mode 100644
index 00000000000..6fb64a55a95
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
@@ -0,0 +1,162 @@
+// 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.authentication;
+
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Persistent metadata for AUTHENTICATION INTEGRATION.
+ */
+public class AuthenticationIntegrationMeta implements Writable {
+ public static final String TYPE_PROPERTY = "type";
+
+ @SerializedName(value = "n")
+ private String name;
+ @SerializedName(value = "t")
+ private String type;
+ @SerializedName(value = "p")
+ private Map<String, String> properties;
+ @SerializedName(value = "c")
+ private String comment;
+
+ private AuthenticationIntegrationMeta() {
+ this.name = "";
+ this.type = "";
+ this.properties = Collections.emptyMap();
+ this.comment = null;
+ }
+
+ public AuthenticationIntegrationMeta(String name, String type, Map<String,
String> properties, String comment) {
+ this.name = Objects.requireNonNull(name, "name can not be null");
+ this.type = Objects.requireNonNull(type, "type can not be null");
+ this.properties = Collections.unmodifiableMap(
+ new LinkedHashMap<>(Objects.requireNonNull(properties,
"properties can not be null")));
+ this.comment = comment;
+ }
+
+ /**
+ * Build metadata from CREATE SQL arguments.
+ */
+ public static AuthenticationIntegrationMeta fromCreateSql(
+ String integrationName, Map<String, String> properties, String
comment) throws DdlException {
+ if (properties == null || properties.isEmpty()) {
+ throw new DdlException("Property 'type' is required in CREATE
AUTHENTICATION INTEGRATION");
+ }
+ String type = null;
+ Map<String, String> copiedProperties = new LinkedHashMap<>();
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ String key = Objects.requireNonNull(entry.getKey(), "property key
can not be null");
+ if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+ if (type != null) {
+ throw new DdlException("Property 'type' is duplicated in
CREATE AUTHENTICATION INTEGRATION");
+ }
+ type = entry.getValue();
+ continue;
+ }
+ copiedProperties.put(key, entry.getValue());
+ }
+ if (type == null || type.isEmpty()) {
+ throw new DdlException("Property 'type' is required in CREATE
AUTHENTICATION INTEGRATION");
+ }
+ return new AuthenticationIntegrationMeta(integrationName, type,
copiedProperties, comment);
+ }
+
+ /**
+ * Build a new metadata object after ALTER ... SET PROPERTIES.
+ */
+ public AuthenticationIntegrationMeta withAlterProperties(Map<String,
String> propertiesDelta) throws DdlException {
+ if (propertiesDelta == null || propertiesDelta.isEmpty()) {
+ throw new DdlException("ALTER AUTHENTICATION INTEGRATION should
contain at least one property");
+ }
+ for (String key : propertiesDelta.keySet()) {
+ if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+ throw new DdlException("ALTER AUTHENTICATION INTEGRATION does
not allow modifying property 'type'");
+ }
+ }
+ Map<String, String> mergedProperties = new LinkedHashMap<>(properties);
+ mergedProperties.putAll(propertiesDelta);
+ return new AuthenticationIntegrationMeta(name, type, mergedProperties,
comment);
+ }
+
+ /**
+ * Build a new metadata object after ALTER ... UNSET PROPERTIES.
+ */
+ public AuthenticationIntegrationMeta withUnsetProperties(Set<String>
propertiesToUnset) throws DdlException {
+ if (propertiesToUnset == null || propertiesToUnset.isEmpty()) {
+ throw new DdlException("ALTER AUTHENTICATION INTEGRATION should
contain at least one property");
+ }
+ Map<String, String> reducedProperties = new
LinkedHashMap<>(properties);
+ for (String key : propertiesToUnset) {
+ if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+ throw new DdlException("ALTER AUTHENTICATION INTEGRATION does
not allow modifying property 'type'");
+ }
+ reducedProperties.remove(key);
+ }
+ return new AuthenticationIntegrationMeta(name, type,
reducedProperties, comment);
+ }
+
+ public AuthenticationIntegrationMeta withComment(String newComment) {
+ return new AuthenticationIntegrationMeta(name, type, properties,
newComment);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public Map<String, String> toSqlPropertiesView() {
+ Map<String, String> allProperties = new LinkedHashMap<>();
+ allProperties.put(TYPE_PROPERTY, type);
+ allProperties.putAll(properties);
+ return allProperties;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ Text.writeString(out, GsonUtils.GSON.toJson(this));
+ }
+
+ public static AuthenticationIntegrationMeta read(DataInput in) throws
IOException {
+ return GsonUtils.GSON.fromJson(Text.readString(in),
AuthenticationIntegrationMeta.class);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
new file mode 100644
index 00000000000..6b821883054
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
@@ -0,0 +1,204 @@
+// 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.authentication;
+
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Manager for AUTHENTICATION INTEGRATION metadata.
+ */
+public class AuthenticationIntegrationMgr implements Writable {
+ private final ReentrantReadWriteLock lock = new
ReentrantReadWriteLock(true);
+
+ @SerializedName(value = "nTi")
+ private Map<String, AuthenticationIntegrationMeta> nameToIntegration = new
LinkedHashMap<>();
+
+ private void readLock() {
+ lock.readLock().lock();
+ }
+
+ private void readUnlock() {
+ lock.readLock().unlock();
+ }
+
+ private void writeLock() {
+ lock.writeLock().lock();
+ }
+
+ private void writeUnlock() {
+ lock.writeLock().unlock();
+ }
+
+ public void createAuthenticationIntegration(
+ String integrationName, boolean ifNotExists, Map<String, String>
properties, String comment)
+ throws DdlException {
+ AuthenticationIntegrationMeta meta =
+ AuthenticationIntegrationMeta.fromCreateSql(integrationName,
properties, comment);
+ writeLock();
+ try {
+ if (nameToIntegration.containsKey(integrationName)) {
+ if (ifNotExists) {
+ return;
+ }
+ throw new DdlException("Authentication integration " +
integrationName + " already exists");
+ }
+ nameToIntegration.put(integrationName, meta);
+ // TODO(authentication-integration): Re-enable edit log persistence
+ // when authentication integration is fully integrated.
+ //
Env.getCurrentEnv().getEditLog().logCreateAuthenticationIntegration(meta);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void alterAuthenticationIntegrationProperties(
+ String integrationName, Map<String, String> properties) throws
DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withAlterProperties(properties);
+ nameToIntegration.put(integrationName, updated);
+ // TODO(authentication-integration): Re-enable edit log persistence
+ // when authentication integration is fully integrated.
+ //
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void alterAuthenticationIntegrationUnsetProperties(
+ String integrationName, Set<String> propertiesToUnset) throws
DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withUnsetProperties(propertiesToUnset);
+ nameToIntegration.put(integrationName, updated);
+ // TODO(authentication-integration): Re-enable edit log persistence
+ // when authentication integration is fully integrated.
+ //
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void alterAuthenticationIntegrationComment(String integrationName,
String comment) throws DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withComment(comment);
+ nameToIntegration.put(integrationName, updated);
+ // TODO(authentication-integration): Re-enable edit log persistence
+ // when authentication integration is fully integrated.
+ //
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void dropAuthenticationIntegration(String integrationName, boolean
ifExists) throws DdlException {
+ writeLock();
+ try {
+ if (!nameToIntegration.containsKey(integrationName)) {
+ if (ifExists) {
+ return;
+ }
+ throw new DdlException("Authentication integration " +
integrationName + " does not exist");
+ }
+ nameToIntegration.remove(integrationName);
+ // TODO(authentication-integration): Re-enable edit log persistence
+ // when authentication integration is fully integrated.
+ //
Env.getCurrentEnv().getEditLog().logDropAuthenticationIntegration(
+ // new
DropAuthenticationIntegrationOperationLog(integrationName));
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void
replayCreateAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+ writeLock();
+ try {
+ nameToIntegration.put(meta.getName(), meta);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void
replayAlterAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+ writeLock();
+ try {
+ nameToIntegration.put(meta.getName(), meta);
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public void
replayDropAuthenticationIntegration(DropAuthenticationIntegrationOperationLog
log) {
+ writeLock();
+ try {
+ nameToIntegration.remove(log.getIntegrationName());
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ public Map<String, AuthenticationIntegrationMeta>
getAuthenticationIntegrations() {
+ readLock();
+ try {
+ return Collections.unmodifiableMap(new
LinkedHashMap<>(nameToIntegration));
+ } finally {
+ readUnlock();
+ }
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ Text.writeString(out, GsonUtils.GSON.toJson(this));
+ }
+
+ public static AuthenticationIntegrationMgr read(DataInput in) throws
IOException {
+ String json = Text.readString(in);
+ AuthenticationIntegrationMgr mgr = GsonUtils.GSON.fromJson(json,
AuthenticationIntegrationMgr.class);
+ if (mgr.nameToIntegration == null) {
+ mgr.nameToIntegration = new LinkedHashMap<>();
+ }
+ return mgr;
+ }
+
+ private AuthenticationIntegrationMeta getOrThrow(String integrationName)
throws DdlException {
+ AuthenticationIntegrationMeta meta =
nameToIntegration.get(integrationName);
+ if (meta == null) {
+ throw new DdlException("Authentication integration " +
integrationName + " does not exist");
+ }
+ return meta;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 77fa6a0f355..45f3d5513bd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -27,6 +27,7 @@ import org.apache.doris.alter.SystemHandler;
import org.apache.doris.analysis.DistributionDesc;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.SlotRef;
+import org.apache.doris.authentication.AuthenticationIntegrationMgr;
import org.apache.doris.backup.BackupHandler;
import org.apache.doris.backup.RestoreJob;
import org.apache.doris.binlog.BinlogGcer;
@@ -375,6 +376,7 @@ public class Env {
private RoutineLoadManager routineLoadManager;
private GroupCommitManager groupCommitManager;
private SqlBlockRuleMgr sqlBlockRuleMgr;
+ private AuthenticationIntegrationMgr authenticationIntegrationMgr;
private ExportMgr exportMgr;
private Alter alter;
private ConsistencyChecker consistencyChecker;
@@ -704,6 +706,7 @@ public class Env {
this.routineLoadManager =
EnvFactory.getInstance().createRoutineLoadManager();
this.groupCommitManager = new GroupCommitManager();
this.sqlBlockRuleMgr = new SqlBlockRuleMgr();
+ this.authenticationIntegrationMgr = new AuthenticationIntegrationMgr();
this.exportMgr = new ExportMgr();
this.alter = new Alter();
this.consistencyChecker = new ConsistencyChecker();
@@ -2488,6 +2491,17 @@ public class Env {
return checksum;
}
+ public long loadAuthenticationIntegrations(DataInputStream in, long
checksum) throws IOException {
+ // TODO(authentication-integration): Re-enable image persistence
+ // when authentication integration is fully integrated.
+ // Consume persisted bytes to keep image stream alignment,
+ // but do not restore into in-memory state for now.
+ AuthenticationIntegrationMgr.read(in);
+ authenticationIntegrationMgr = new AuthenticationIntegrationMgr();
+ LOG.info("skip replay authentication integrations from image
temporarily");
+ return checksum;
+ }
+
/**
* Load policy through file.
**/
@@ -2800,6 +2814,14 @@ public class Env {
return checksum;
}
+ public long saveAuthenticationIntegrations(CountingDataOutputStream out,
long checksum) throws IOException {
+ // TODO(authentication-integration): Re-enable image persistence
+ // when authentication integration is fully integrated.
+ // Persist an empty manager temporarily.
+ new AuthenticationIntegrationMgr().write(out);
+ return checksum;
+ }
+
public long savePolicy(CountingDataOutputStream out, long checksum) throws
IOException {
Env.getCurrentEnv().getPolicyMgr().write(out);
return checksum;
@@ -5152,6 +5174,10 @@ public class Env {
return sqlBlockRuleMgr;
}
+ public AuthenticationIntegrationMgr getAuthenticationIntegrationMgr() {
+ return authenticationIntegrationMgr;
+ }
+
public RoutineLoadTaskScheduler getRoutineLoadTaskScheduler() {
return routineLoadTaskScheduler;
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
index 8f5e3f17865..0f1712110f1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
@@ -21,6 +21,7 @@ import org.apache.doris.alter.AlterJobV2;
import org.apache.doris.alter.BatchAlterJobPersistInfo;
import org.apache.doris.alter.IndexChangeJob;
import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
import org.apache.doris.backup.BackupJob;
import org.apache.doris.backup.Repository;
import org.apache.doris.backup.RestoreJob;
@@ -89,6 +90,7 @@ import org.apache.doris.persist.CreateTableInfo;
import org.apache.doris.persist.DatabaseInfo;
import org.apache.doris.persist.DictionaryDecreaseVersionInfo;
import org.apache.doris.persist.DictionaryIncreaseVersionInfo;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
import org.apache.doris.persist.DropDbInfo;
import org.apache.doris.persist.DropDictionaryPersistInfo;
import org.apache.doris.persist.DropInfo;
@@ -709,6 +711,17 @@ public class JournalEntity implements Writable {
isRead = true;
break;
}
+ case OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION:
+ case OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION: {
+ data = AuthenticationIntegrationMeta.read(in);
+ isRead = true;
+ break;
+ }
+ case OperationType.OP_DROP_AUTHENTICATION_INTEGRATION: {
+ data = DropAuthenticationIntegrationOperationLog.read(in);
+ isRead = true;
+ break;
+ }
case OperationType.OP_MODIFY_TABLE_ENGINE: {
data = ModifyTableEngineOperationLog.read(in);
isRead = true;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 2675541b4f8..9970b7a4e5f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -634,6 +634,7 @@ import
org.apache.doris.nereids.trees.plans.commands.AdminSetPartitionVersionCom
import
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaStatusCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaVersionCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminSetTableStatusCommand;
+import
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
import
org.apache.doris.nereids.trees.plans.commands.AlterCatalogCommentCommand;
import
org.apache.doris.nereids.trees.plans.commands.AlterCatalogPropertiesCommand;
import org.apache.doris.nereids.trees.plans.commands.AlterCatalogRenameCommand;
@@ -675,6 +676,7 @@ import
org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
import org.apache.doris.nereids.trees.plans.commands.Command;
import org.apache.doris.nereids.trees.plans.commands.Constraint;
import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
+import
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateDictionaryCommand;
@@ -706,6 +708,7 @@ import
org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand;
import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand;
import org.apache.doris.nereids.trees.plans.commands.DescribeCommand;
import org.apache.doris.nereids.trees.plans.commands.DropAnalyzeJobCommand;
+import
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
import org.apache.doris.nereids.trees.plans.commands.DropCachedStatsCommand;
import org.apache.doris.nereids.trees.plans.commands.DropCatalogCommand;
import
org.apache.doris.nereids.trees.plans.commands.DropCatalogRecycleBinCommand;
@@ -2121,6 +2124,18 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return propertiesMap.build();
}
+ @Override
+ public Set<String>
visitPropertyKeyClause(DorisParser.PropertyKeyClauseContext ctx) {
+ if (ctx == null || ctx.keys == null) {
+ return ImmutableSet.of();
+ }
+ ImmutableSet.Builder<String> propertyKeys = ImmutableSet.builder();
+ for (PropertyKeyContext propertyKey : ctx.keys) {
+ propertyKeys.add(parsePropertyKey(propertyKey));
+ }
+ return propertyKeys.build();
+ }
+
@Override
public BrokerDesc
visitWithRemoteStorageSystem(WithRemoteStorageSystemContext ctx) {
BrokerDesc brokerDesc = null;
@@ -5042,6 +5057,15 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return item.getText();
}
+ private boolean containsPropertyKeyIgnoreCase(Iterable<String>
propertyKeys, String expectedKey) {
+ for (String key : propertyKeys) {
+ if (key.equalsIgnoreCase(expectedKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private ExplainLevel parseExplainPlanType(PlanTypeContext planTypeContext)
{
if (planTypeContext == null || planTypeContext.ALL() != null) {
return ExplainLevel.ALL_PLAN;
@@ -7061,6 +7085,19 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return new CreateCatalogCommand(catalogName, ifNotExists,
resourceName, comment, properties);
}
+ @Override
+ public LogicalPlan visitCreateAuthenticationIntegration(
+ DorisParser.CreateAuthenticationIntegrationContext ctx) {
+ boolean ifNotExists = ctx.IF() != null;
+ String integrationName = stripQuotes(ctx.integrationName.getText());
+ Map<String, String> properties =
Maps.newHashMap(visitPropertyClause(ctx.properties));
+ if (!containsPropertyKeyIgnoreCase(properties.keySet(), "type")) {
+ throw new ParseException("Property 'type' is required in CREATE
AUTHENTICATION INTEGRATION", ctx);
+ }
+ String comment = ctx.commentSpec() == null ? null :
stripQuotes(ctx.commentSpec().STRING_LITERAL().getText());
+ return new CreateAuthenticationIntegrationCommand(integrationName,
ifNotExists, properties, comment);
+ }
+
@Override
public LogicalPlan visitShowStages(ShowStagesContext ctx) {
return new ShowStagesCommand();
@@ -7097,6 +7134,38 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return new AlterCatalogPropertiesCommand(catalogName, properties);
}
+ @Override
+ public LogicalPlan visitAlterAuthenticationIntegrationProperties(
+ DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) {
+ String integrationName = stripQuotes(ctx.integrationName.getText());
+ Map<String, String> properties =
Maps.newHashMap(visitPropertyClause(ctx.properties));
+ if (containsPropertyKeyIgnoreCase(properties.keySet(), "type")) {
+ throw new ParseException(
+ "ALTER AUTHENTICATION INTEGRATION does not allow modifying
property 'type'", ctx);
+ }
+ return
AlterAuthenticationIntegrationCommand.forSetProperties(integrationName,
properties);
+ }
+
+ @Override
+ public LogicalPlan visitAlterAuthenticationIntegrationUnsetProperties(
+ DorisParser.AlterAuthenticationIntegrationUnsetPropertiesContext
ctx) {
+ String integrationName = stripQuotes(ctx.integrationName.getText());
+ Set<String> unsetProperties = visitPropertyKeyClause(ctx.properties);
+ if (containsPropertyKeyIgnoreCase(unsetProperties, "type")) {
+ throw new ParseException(
+ "ALTER AUTHENTICATION INTEGRATION does not allow modifying
property 'type'", ctx);
+ }
+ return
AlterAuthenticationIntegrationCommand.forUnsetProperties(integrationName,
unsetProperties);
+ }
+
+ @Override
+ public LogicalPlan visitAlterAuthenticationIntegrationComment(
+ DorisParser.AlterAuthenticationIntegrationCommentContext ctx) {
+ String integrationName = stripQuotes(ctx.integrationName.getText());
+ String comment = stripQuotes(ctx.comment.getText());
+ return
AlterAuthenticationIntegrationCommand.forSetComment(integrationName, comment);
+ }
+
@Override
public RecoverTableCommand visitRecoverTable(RecoverTableContext ctx) {
List<String> dbTblNameParts = visitMultipartIdentifier(ctx.name);
@@ -7215,6 +7284,14 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return new DropCatalogCommand(catalogName, ifExists);
}
+ @Override
+ public LogicalPlan visitDropAuthenticationIntegration(
+ DorisParser.DropAuthenticationIntegrationContext ctx) {
+ String integrationName = stripQuotes(ctx.name.getText());
+ boolean ifExists = ctx.EXISTS() != null;
+ return new DropAuthenticationIntegrationCommand(ifExists,
integrationName);
+ }
+
@Override
public LogicalPlan visitCreateEncryptkey(CreateEncryptkeyContext ctx) {
List<String> nameParts =
visitMultipartIdentifier(ctx.multipartIdentifier());
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
index 3c9eea1d386..0555ea108dc 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
@@ -132,6 +132,18 @@ public class LogicalPlanBuilderForEncryption extends
LogicalPlanBuilder {
return super.visitCreateStorageVault(ctx);
}
+ // create authentication integration clause
+ @Override
+ public LogicalPlan
visitCreateAuthenticationIntegration(DorisParser.CreateAuthenticationIntegrationContext
ctx) {
+ if (ctx.properties != null && ctx.properties.fileProperties != null) {
+ DorisParser.PropertyClauseContext propertyClauseContext =
ctx.properties;
+ encryptProperty(visitPropertyClause(propertyClauseContext),
+ propertyClauseContext.fileProperties.start.getStartIndex(),
+ propertyClauseContext.fileProperties.stop.getStopIndex());
+ }
+ return super.visitCreateAuthenticationIntegration(ctx);
+ }
+
// alter storage vault clause
@Override
public LogicalPlan
visitAlterStorageVault(DorisParser.AlterStorageVaultContext ctx) {
@@ -144,6 +156,19 @@ public class LogicalPlanBuilderForEncryption extends
LogicalPlanBuilder {
return super.visitAlterStorageVault(ctx);
}
+ // alter authentication integration properties clause
+ @Override
+ public LogicalPlan visitAlterAuthenticationIntegrationProperties(
+ DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) {
+ if (ctx.properties != null && ctx.properties.fileProperties != null) {
+ DorisParser.PropertyClauseContext propertyClauseContext =
ctx.properties;
+ encryptProperty(visitPropertyClause(propertyClauseContext),
+ propertyClauseContext.fileProperties.start.getStartIndex(),
+ propertyClauseContext.fileProperties.stop.getStopIndex());
+ }
+ return super.visitAlterAuthenticationIntegrationProperties(ctx);
+ }
+
// select from tvf
@Override
public LogicalPlan
visitTableValuedFunction(DorisParser.TableValuedFunctionContext ctx) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index dcff41a3175..486d23b9216 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -158,6 +158,7 @@ public enum PlanType {
COPY_INTO_COMMAND,
CREATE_POLICY_COMMAND,
CREATE_TABLE_COMMAND,
+ CREATE_AUTHENTICATION_INTEGRATION_COMMAND,
CREATE_DICTIONARY_COMMAND,
DROP_DICTIONARY_COMMAND,
CREATE_SQL_BLOCK_RULE_COMMAND,
@@ -186,10 +187,12 @@ public enum PlanType {
CANCEL_JOB_COMMAND,
DROP_CATALOG_COMMAND,
DROP_DATABASE_COMMAND,
+ DROP_AUTHENTICATION_INTEGRATION_COMMAND,
DROP_JOB_COMMAND,
RESUME_JOB_COMMAND,
ALTER_MTMV_COMMAND,
ALTER_CATALOG_PROPERTIES_COMMAND,
+ ALTER_AUTHENTICATION_INTEGRATION_COMMAND,
ADD_CONSTRAINT_COMMAND,
ADMIN_COMPACT_TABLE_COMMAND,
DROP_CONSTRAINT_COMMAND,
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..2955d43dc54
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
@@ -0,0 +1,135 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ALTER AUTHENTICATION INTEGRATION command entry.
+ */
+public class AlterAuthenticationIntegrationCommand extends AlterCommand
implements NeedAuditEncryption {
+ /** alter action. */
+ public enum AlterType {
+ SET_PROPERTIES,
+ UNSET_PROPERTIES,
+ SET_COMMENT
+ }
+
+ private final String integrationName;
+ private final AlterType alterType;
+ private final Map<String, String> properties;
+ private final Set<String> unsetProperties;
+ private final String comment;
+
+ private AlterAuthenticationIntegrationCommand(String integrationName,
AlterType alterType,
+ Map<String, String> properties, Set<String> unsetProperties,
String comment) {
+ super(PlanType.ALTER_AUTHENTICATION_INTEGRATION_COMMAND);
+ this.integrationName = Objects.requireNonNull(integrationName,
"integrationName can not be null");
+ this.alterType = Objects.requireNonNull(alterType, "alterType can not
be null");
+ this.properties = Collections.unmodifiableMap(
+ new LinkedHashMap<>(Objects.requireNonNull(properties,
"properties can not be null")));
+ this.unsetProperties = Collections.unmodifiableSet(
+ new LinkedHashSet<>(Objects.requireNonNull(unsetProperties,
"unsetProperties can not be null")));
+ this.comment = comment;
+ }
+
+ public static AlterAuthenticationIntegrationCommand
forSetProperties(String integrationName,
+ Map<String, String> properties) {
+ return new AlterAuthenticationIntegrationCommand(
+ integrationName, AlterType.SET_PROPERTIES, properties,
Collections.emptySet(), null);
+ }
+
+ public static AlterAuthenticationIntegrationCommand
forUnsetProperties(String integrationName,
+ Set<String> unsetProperties) {
+ return new AlterAuthenticationIntegrationCommand(
+ integrationName, AlterType.UNSET_PROPERTIES,
Collections.emptyMap(), unsetProperties, null);
+ }
+
+ public static AlterAuthenticationIntegrationCommand forSetComment(String
integrationName, String comment) {
+ return new AlterAuthenticationIntegrationCommand(
+ integrationName, AlterType.SET_COMMENT,
Collections.emptyMap(), Collections.emptySet(), comment);
+ }
+
+ @Override
+ public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+ return visitor.visitAlterAuthenticationIntegrationCommand(this,
context);
+ }
+
+ @Override
+ public void doRun(ConnectContext ctx, StmtExecutor executor) throws
Exception {
+ if
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(),
PrivPredicate.ADMIN)) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ADMIN");
+ }
+ switch (alterType) {
+ case SET_PROPERTIES:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationProperties(integrationName, properties);
+ return;
+ case UNSET_PROPERTIES:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationUnsetProperties(integrationName,
unsetProperties);
+ return;
+ case SET_COMMENT:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationComment(integrationName, comment);
+ return;
+ default:
+ throw new AnalysisException("Unsupported alter type for
AUTHENTICATION INTEGRATION: " + alterType);
+ }
+ }
+
+ @Override
+ public boolean needAuditEncryption() {
+ return true;
+ }
+
+ public String getIntegrationName() {
+ return integrationName;
+ }
+
+ public AlterType getAlterType() {
+ return alterType;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ public Set<String> getUnsetProperties() {
+ return unsetProperties;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..af0531eb871
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
@@ -0,0 +1,94 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * CREATE AUTHENTICATION INTEGRATION command entry.
+ */
+public class CreateAuthenticationIntegrationCommand extends Command implements
ForwardWithSync, NeedAuditEncryption {
+ private final String integrationName;
+ private final boolean ifNotExists;
+ private final Map<String, String> properties;
+ private final String comment;
+
+ /** Constructor. */
+ public CreateAuthenticationIntegrationCommand(String integrationName,
boolean ifNotExists,
+ Map<String, String> properties, String comment) {
+ super(PlanType.CREATE_AUTHENTICATION_INTEGRATION_COMMAND);
+ this.integrationName = Objects.requireNonNull(integrationName,
"integrationName can not be null");
+ this.ifNotExists = ifNotExists;
+ this.properties = Collections.unmodifiableMap(
+ new LinkedHashMap<>(Objects.requireNonNull(properties,
"properties can not be null")));
+ this.comment = comment;
+ }
+
+ @Override
+ public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+ return visitor.visitCreateAuthenticationIntegrationCommand(this,
context);
+ }
+
+ @Override
+ public void run(ConnectContext ctx, StmtExecutor executor) throws
Exception {
+ if
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(),
PrivPredicate.ADMIN)) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ADMIN");
+ }
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+ .createAuthenticationIntegration(integrationName, ifNotExists,
properties, comment);
+ }
+
+ @Override
+ public StmtType stmtType() {
+ return StmtType.CREATE;
+ }
+
+ @Override
+ public boolean needAuditEncryption() {
+ return true;
+ }
+
+ public String getIntegrationName() {
+ return integrationName;
+ }
+
+ public boolean isSetIfNotExists() {
+ return ifNotExists;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..aa041d44678
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
@@ -0,0 +1,65 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Objects;
+
+/**
+ * DROP AUTHENTICATION INTEGRATION command entry.
+ */
+public class DropAuthenticationIntegrationCommand extends DropCommand {
+ private final boolean ifExists;
+ private final String integrationName;
+
+ public DropAuthenticationIntegrationCommand(boolean ifExists, String
integrationName) {
+ super(PlanType.DROP_AUTHENTICATION_INTEGRATION_COMMAND);
+ this.ifExists = ifExists;
+ this.integrationName = Objects.requireNonNull(integrationName,
"integrationName can not be null");
+ }
+
+ @Override
+ public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+ return visitor.visitDropAuthenticationIntegrationCommand(this,
context);
+ }
+
+ @Override
+ public void doRun(ConnectContext ctx, StmtExecutor executor) throws
Exception {
+ if
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(),
PrivPredicate.ADMIN)) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ADMIN");
+ }
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+ .dropAuthenticationIntegration(integrationName, ifExists);
+ }
+
+ public boolean isIfExists() {
+ return ifExists;
+ }
+
+ public String getIntegrationName() {
+ return integrationName;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
index 0f1625c629b..78b5f83395f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
@@ -36,6 +36,7 @@ import
org.apache.doris.nereids.trees.plans.commands.AdminSetPartitionVersionCom
import
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaStatusCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaVersionCommand;
import
org.apache.doris.nereids.trees.plans.commands.AdminSetTableStatusCommand;
+import
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
import
org.apache.doris.nereids.trees.plans.commands.AlterCatalogCommentCommand;
import
org.apache.doris.nereids.trees.plans.commands.AlterCatalogPropertiesCommand;
import org.apache.doris.nereids.trees.plans.commands.AlterCatalogRenameCommand;
@@ -72,6 +73,7 @@ import
org.apache.doris.nereids.trees.plans.commands.CleanAllProfileCommand;
import org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
import org.apache.doris.nereids.trees.plans.commands.Command;
import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
+import
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateDictionaryCommand;
@@ -103,6 +105,7 @@ import
org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand;
import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand;
import org.apache.doris.nereids.trees.plans.commands.DescribeCommand;
import org.apache.doris.nereids.trees.plans.commands.DropAnalyzeJobCommand;
+import
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
import org.apache.doris.nereids.trees.plans.commands.DropCachedStatsCommand;
import org.apache.doris.nereids.trees.plans.commands.DropCatalogCommand;
import
org.apache.doris.nereids.trees.plans.commands.DropCatalogRecycleBinCommand;
@@ -517,6 +520,11 @@ public interface CommandVisitor<R, C> {
return visitCommand(createCatalogCommand, context);
}
+ default R visitCreateAuthenticationIntegrationCommand(
+ CreateAuthenticationIntegrationCommand
createAuthenticationIntegrationCommand, C context) {
+ return visitCommand(createAuthenticationIntegrationCommand, context);
+ }
+
default R visitShowWarningErrorsCommand(ShowWarningErrorsCommand
showWarningErrorsCommand, C context) {
return visitCommand(showWarningErrorsCommand, context);
}
@@ -545,6 +553,11 @@ public interface CommandVisitor<R, C> {
return visitCommand(dropCatalogCommand, context);
}
+ default R visitDropAuthenticationIntegrationCommand(
+ DropAuthenticationIntegrationCommand
dropAuthenticationIntegrationCommand, C context) {
+ return visitCommand(dropAuthenticationIntegrationCommand, context);
+ }
+
default R visitAlterCatalogCommentCommand(AlterCatalogCommentCommand
alterCatalogCommentCommand, C context) {
return visitCommand(alterCatalogCommentCommand, context);
}
@@ -868,6 +881,11 @@ public interface CommandVisitor<R, C> {
return visitCommand(alterCatalogPropsCmd, context);
}
+ default R visitAlterAuthenticationIntegrationCommand(
+ AlterAuthenticationIntegrationCommand
alterAuthenticationIntegrationCommand, C context) {
+ return visitCommand(alterAuthenticationIntegrationCommand, context);
+ }
+
default R
visitAlterDatabasePropertiesCommand(AlterDatabasePropertiesCommand
alterDatabasePropsCmd, C context) {
return visitCommand(alterDatabasePropsCmd, context);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
new file mode 100644
index 00000000000..a2f2015cdc2
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
@@ -0,0 +1,53 @@
+// 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.persist;
+
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Drop log for AUTHENTICATION INTEGRATION.
+ */
+public class DropAuthenticationIntegrationOperationLog implements Writable {
+ @SerializedName(value = "in")
+ private String integrationName;
+
+ public DropAuthenticationIntegrationOperationLog(String integrationName) {
+ this.integrationName = integrationName;
+ }
+
+ public String getIntegrationName() {
+ return integrationName;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ Text.writeString(out, GsonUtils.GSON.toJson(this));
+ }
+
+ public static DropAuthenticationIntegrationOperationLog read(DataInput in)
throws IOException {
+ return GsonUtils.GSON.fromJson(Text.readString(in),
DropAuthenticationIntegrationOperationLog.class);
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
index c7384c7e715..2fd5097ff50 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
@@ -22,6 +22,7 @@ import org.apache.doris.alter.AlterJobV2.JobState;
import org.apache.doris.alter.BatchAlterJobPersistInfo;
import org.apache.doris.alter.IndexChangeJob;
import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
import org.apache.doris.backup.BackupJob;
import org.apache.doris.backup.Repository;
import org.apache.doris.backup.RestoreJob;
@@ -1083,6 +1084,22 @@ public class EditLog {
env.getSqlBlockRuleMgr().replayDrop(log.getRuleNames());
break;
}
+ case OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION: {
+ AuthenticationIntegrationMeta log =
(AuthenticationIntegrationMeta) journal.getData();
+
env.getAuthenticationIntegrationMgr().replayCreateAuthenticationIntegration(log);
+ break;
+ }
+ case OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION: {
+ AuthenticationIntegrationMeta log =
(AuthenticationIntegrationMeta) journal.getData();
+
env.getAuthenticationIntegrationMgr().replayAlterAuthenticationIntegration(log);
+ break;
+ }
+ case OperationType.OP_DROP_AUTHENTICATION_INTEGRATION: {
+ DropAuthenticationIntegrationOperationLog log =
+ (DropAuthenticationIntegrationOperationLog)
journal.getData();
+
env.getAuthenticationIntegrationMgr().replayDropAuthenticationIntegration(log);
+ break;
+ }
case OperationType.OP_MODIFY_TABLE_ENGINE: {
ModifyTableEngineOperationLog log =
(ModifyTableEngineOperationLog) journal.getData();
env.getAlterInstance().replayProcessModifyEngine(log);
@@ -2318,6 +2335,18 @@ public class EditLog {
logEdit(OperationType.OP_DROP_SQL_BLOCK_RULE, new
DropSqlBlockRuleOperationLog(ruleNames));
}
+ public void
logCreateAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+ logEdit(OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION, meta);
+ }
+
+ public void
logAlterAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+ logEdit(OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION, meta);
+ }
+
+ public void
logDropAuthenticationIntegration(DropAuthenticationIntegrationOperationLog log)
{
+ logEdit(OperationType.OP_DROP_AUTHENTICATION_INTEGRATION, log);
+ }
+
public void logModifyTableEngine(ModifyTableEngineOperationLog log) {
logEdit(OperationType.OP_MODIFY_TABLE_ENGINE, log);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
index 016903129c2..96bb4669e41 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
@@ -418,6 +418,9 @@ public class OperationType {
public static final short OP_DROP_INDEX_POLICY = 491;
public static final short OP_OPERATE_KEY = 492;
+ public static final short OP_CREATE_AUTHENTICATION_INTEGRATION = 493;
+ public static final short OP_ALTER_AUTHENTICATION_INTEGRATION = 494;
+ public static final short OP_DROP_AUTHENTICATION_INTEGRATION = 495;
// For cloud.
public static final short OP_UPDATE_CLOUD_REPLICA = 1000;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
index 0114bde9eee..ea3585d493b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
@@ -186,6 +186,15 @@ public class MetaPersistMethod {
metaPersistMethod.writeMethod =
Env.class.getDeclaredMethod("saveSqlBlockRule",
CountingDataOutputStream.class, long.class);
break;
+ // TODO: Re-enable this module once AuthenticationIntegrations
should be persisted again.
+ // case "authenticationIntegrations":
+ // metaPersistMethod.readMethod =
+ //
Env.class.getDeclaredMethod("loadAuthenticationIntegrations",
DataInputStream.class,
+ // long.class);
+ // metaPersistMethod.writeMethod =
+ //
Env.class.getDeclaredMethod("saveAuthenticationIntegrations",
+ // CountingDataOutputStream.class, long.class);
+ // break;
case "policy":
metaPersistMethod.readMethod =
Env.class.getDeclaredMethod("loadPolicy",
DataInputStream.class, long.class);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
index 665f3cb09ed..44809035406 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
@@ -40,9 +40,13 @@ public class PersistMetaModules {
"masterInfo", "frontends", "backends", "datasource", "db",
"alterJob", "recycleBin",
"globalVariable", "cluster", "broker", "resources", "exportJob",
"backupHandler",
"paloAuth", "transactionState", "colocateTableIndex",
"routineLoadJobs", "loadJobV2", "smallFiles",
- "plugins", "deleteHandler", "sqlBlockRule", "policy",
"globalFunction", "workloadGroups",
+ "plugins", "deleteHandler", "sqlBlockRule", "policy",
+ "globalFunction", "workloadGroups",
"binlogs", "resourceGroups", "AnalysisMgrV2", "AsyncJobManager",
"workloadSchedPolicy",
- "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy",
"KeyManagerStore");
+ "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy",
"KeyManagerStore"
+ // TODO: Re-enable "authenticationIntegrations" after persistence
requirements are confirmed.
+ // , "authenticationIntegrations"
+ );
// The modules in `CloudEnv`.
public static final ImmutableList<String> CLOUD_MODULE_NAMES =
ImmutableList.of("cloudWarmUpJob");
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
new file mode 100644
index 00000000000..b11268c7cc3
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
@@ -0,0 +1,168 @@
+// 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.authentication;
+
+import org.apache.doris.common.DdlException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationMetaTest {
+
+ private static Map<String, String> map(String... kvs) {
+ Map<String, String> result = new LinkedHashMap<>();
+ for (int i = 0; i < kvs.length; i += 2) {
+ result.put(kvs[i], kvs[i + 1]);
+ }
+ return result;
+ }
+
+ private static Set<String> set(String... keys) {
+ Set<String> result = new LinkedHashSet<>();
+ Collections.addAll(result, keys);
+ return result;
+ }
+
+ @Test
+ public void testFromCreateSqlSuccessAndTypeFiltered() throws Exception {
+ Map<String, String> properties = new LinkedHashMap<>();
+ properties.put("TYPE", "ldap");
+ properties.put("ldap.server", "ldap://127.0.0.1:389");
+ properties.put("ldap.admin_dn", "cn=admin,dc=example,dc=com");
+
+ AuthenticationIntegrationMeta meta =
+ AuthenticationIntegrationMeta.fromCreateSql("corp_ldap",
properties, "ldap integration");
+
+ Assertions.assertEquals("corp_ldap", meta.getName());
+ Assertions.assertEquals("ldap", meta.getType());
+ Assertions.assertEquals("ldap integration", meta.getComment());
+ Assertions.assertEquals(2, meta.getProperties().size());
+ Assertions.assertEquals("ldap://127.0.0.1:389",
meta.getProperties().get("ldap.server"));
+ Assertions.assertFalse(meta.getProperties().containsKey("type"));
+ Assertions.assertFalse(meta.getProperties().containsKey("TYPE"));
+
+ Assertions.assertThrows(UnsupportedOperationException.class,
+ () -> meta.getProperties().put("x", "y"));
+
+ Map<String, String> sqlProperties = meta.toSqlPropertiesView();
+ Assertions.assertEquals("ldap", sqlProperties.get("type"));
+ Assertions.assertEquals("cn=admin,dc=example,dc=com",
sqlProperties.get("ldap.admin_dn"));
+ }
+
+ @Test
+ public void testFromCreateSqlRequireType() {
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1", null,
null));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
Collections.emptyMap(), null));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
map("k", "v"), null));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
map("type", ""), null));
+ }
+
+ @Test
+ public void testFromCreateSqlRejectDuplicatedTypeIgnoreCase() {
+ Map<String, String> properties = new LinkedHashMap<>();
+ properties.put("type", "ldap");
+ properties.put("TYPE", "oidc");
+
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
properties, null));
+ }
+
+ @Test
+ public void testWithAlterProperties() throws Exception {
+ AuthenticationIntegrationMeta meta =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap",
+ map("type", "ldap",
+ "ldap.server", "ldap://old",
+ "ldap.base_dn", "dc=example,dc=com"),
+ "old comment");
+
+ AuthenticationIntegrationMeta altered = meta.withAlterProperties(map(
+ "ldap.server", "ldap://new",
+ "ldap.user_filter", "(uid={login})"));
+
+ Assertions.assertEquals("ldap", altered.getType());
+ Assertions.assertEquals("old comment", altered.getComment());
+ Assertions.assertEquals("ldap://new",
altered.getProperties().get("ldap.server"));
+ Assertions.assertEquals("(uid={login})",
altered.getProperties().get("ldap.user_filter"));
+
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withAlterProperties(Collections.emptyMap()));
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withAlterProperties(map("TYPE", "oidc")));
+ }
+
+ @Test
+ public void testWithUnsetProperties() throws Exception {
+ AuthenticationIntegrationMeta meta =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap",
+ map("type", "ldap",
+ "ldap.server", "ldap://old",
+ "ldap.base_dn", "dc=example,dc=com"),
+ "old comment");
+
+ AuthenticationIntegrationMeta altered =
meta.withUnsetProperties(set("ldap.base_dn"));
+ Assertions.assertEquals("ldap", altered.getType());
+
Assertions.assertFalse(altered.getProperties().containsKey("ldap.base_dn"));
+ Assertions.assertEquals("ldap://old",
altered.getProperties().get("ldap.server"));
+
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withUnsetProperties(Collections.emptySet()));
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withUnsetProperties(set("TYPE")));
+ }
+
+ @Test
+ public void testWriteReadRoundTrip() throws IOException, DdlException {
+ AuthenticationIntegrationMeta meta =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap",
+ map("type", "ldap",
+ "ldap.server", "ldap://127.0.0.1:389",
+ "ldap.admin_password", "123456"),
+ "comment");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (DataOutputStream dos = new DataOutputStream(bos)) {
+ meta.write(dos);
+ }
+
+ AuthenticationIntegrationMeta read;
+ try (DataInputStream dis = new DataInputStream(new
ByteArrayInputStream(bos.toByteArray()))) {
+ read = AuthenticationIntegrationMeta.read(dis);
+ }
+
+ Assertions.assertEquals(meta.getName(), read.getName());
+ Assertions.assertEquals(meta.getType(), read.getType());
+ Assertions.assertEquals(meta.getComment(), read.getComment());
+ Assertions.assertEquals(meta.getProperties(), read.getProperties());
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
new file mode 100644
index 00000000000..18f1dd530c7
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
@@ -0,0 +1,206 @@
+// 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.authentication;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
+import org.apache.doris.persist.EditLog;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationMgrTest {
+
+ @Mocked
+ private Env env;
+
+ @Mocked
+ private EditLog editLog;
+
+ private static Map<String, String> map(String... kvs) {
+ Map<String, String> result = new LinkedHashMap<>();
+ for (int i = 0; i < kvs.length; i += 2) {
+ result.put(kvs[i], kvs[i + 1]);
+ }
+ return result;
+ }
+
+ private static Set<String> set(String... keys) {
+ Set<String> result = new LinkedHashSet<>();
+ Collections.addAll(result, keys);
+ return result;
+ }
+
+ @Test
+ public void testCreateAlterDropFlow() throws Exception {
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getEditLog();
+ minTimes = 0;
+ result = editLog;
+
+
editLog.logCreateAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+ minTimes = 0;
+
+
editLog.logAlterAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+ minTimes = 0;
+
+
editLog.logDropAuthenticationIntegration((DropAuthenticationIntegrationOperationLog)
any);
+ minTimes = 0;
+ }
+ };
+
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+ Map<String, String> createProperties = new LinkedHashMap<>();
+ createProperties.put("type", "ldap");
+ createProperties.put("ldap.server", "ldap://127.0.0.1:389");
+ createProperties.put("ldap.admin_password", "123456");
+
+ mgr.createAuthenticationIntegration("corp_ldap", false,
createProperties, "comment");
+ AuthenticationIntegrationMeta created =
mgr.getAuthenticationIntegrations().get("corp_ldap");
+ Assertions.assertNotNull(created);
+ Assertions.assertEquals("ldap", created.getType());
+ Assertions.assertEquals("ldap://127.0.0.1:389",
created.getProperties().get("ldap.server"));
+
+ mgr.alterAuthenticationIntegrationProperties("corp_ldap",
map("ldap.server", "ldap://127.0.0.1:1389"));
+ Assertions.assertEquals("ldap://127.0.0.1:1389",
+
mgr.getAuthenticationIntegrations().get("corp_ldap").getProperties().get("ldap.server"));
+
+ mgr.alterAuthenticationIntegrationUnsetProperties("corp_ldap",
set("ldap.admin_password"));
+ Assertions.assertFalse(mgr.getAuthenticationIntegrations()
+
.get("corp_ldap").getProperties().containsKey("ldap.admin_password"));
+
+ mgr.alterAuthenticationIntegrationComment("corp_ldap", "new comment");
+ Assertions.assertEquals("new comment",
mgr.getAuthenticationIntegrations().get("corp_ldap").getComment());
+
+ mgr.dropAuthenticationIntegration("corp_ldap", false);
+ Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty());
+ }
+
+ @Test
+ public void testCreateDuplicateAndDropIfExists() throws Exception {
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getEditLog();
+ minTimes = 0;
+ result = editLog;
+
+
editLog.logCreateAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+ minTimes = 0;
+
+
editLog.logDropAuthenticationIntegration((DropAuthenticationIntegrationOperationLog)
any);
+ minTimes = 0;
+ }
+ };
+
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+ mgr.createAuthenticationIntegration("corp_ldap", false, map(
+ "type", "ldap",
+ "ldap.server", "ldap://127.0.0.1:389"), null);
+
+ Assertions.assertThrows(DdlException.class,
+ () -> mgr.createAuthenticationIntegration("corp_ldap", false,
map("type", "ldap"), null));
+ Assertions.assertDoesNotThrow(
+ () -> mgr.createAuthenticationIntegration("corp_ldap", true,
map("type", "ldap"), null));
+
+ Assertions.assertDoesNotThrow(() ->
mgr.dropAuthenticationIntegration("not_exist", true));
+ Assertions.assertThrows(DdlException.class,
+ () -> mgr.dropAuthenticationIntegration("not_exist", false));
+ }
+
+ @Test
+ public void testAlterNotExistThrows() {
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+ Assertions.assertThrows(DdlException.class,
+ () ->
mgr.alterAuthenticationIntegrationProperties("not_exist", map("k", "v")));
+ Assertions.assertThrows(DdlException.class,
+ () ->
mgr.alterAuthenticationIntegrationUnsetProperties("not_exist", set("k")));
+ Assertions.assertThrows(DdlException.class,
+ () -> mgr.alterAuthenticationIntegrationComment("not_exist",
"comment"));
+ }
+
+ @Test
+ public void testReplayAndGetUnmodifiableView() throws Exception {
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+
+ AuthenticationIntegrationMeta meta1 =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap", map("type", "ldap", "ldap.server", "ldap://old"),
null);
+ AuthenticationIntegrationMeta meta2 =
meta1.withAlterProperties(map("ldap.server", "ldap://new"));
+
+ mgr.replayCreateAuthenticationIntegration(meta1);
+ mgr.replayAlterAuthenticationIntegration(meta2);
+
+ Map<String, AuthenticationIntegrationMeta> copy =
mgr.getAuthenticationIntegrations();
+ Assertions.assertEquals(1, copy.size());
+ Assertions.assertThrows(UnsupportedOperationException.class,
+ () -> copy.put("x", meta1));
+
+ mgr.replayDropAuthenticationIntegration(new
DropAuthenticationIntegrationOperationLog("corp_ldap"));
+ Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty());
+ }
+
+ @Test
+ public void testWriteReadRoundTrip() throws IOException, DdlException {
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+ AuthenticationIntegrationMeta meta =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap", map(
+ "type", "ldap",
+ "ldap.server", "ldap://127.0.0.1:389"),
+ "comment");
+ mgr.replayCreateAuthenticationIntegration(meta);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (DataOutputStream dos = new DataOutputStream(bos)) {
+ mgr.write(dos);
+ }
+
+ AuthenticationIntegrationMgr read;
+ try (DataInputStream dis = new DataInputStream(new
ByteArrayInputStream(bos.toByteArray()))) {
+ read = AuthenticationIntegrationMgr.read(dis);
+ }
+
+ Assertions.assertEquals(1,
read.getAuthenticationIntegrations().size());
+ AuthenticationIntegrationMeta readMeta =
read.getAuthenticationIntegrations().get("corp_ldap");
+ Assertions.assertNotNull(readMeta);
+ Assertions.assertEquals("ldap", readMeta.getType());
+ Assertions.assertEquals("ldap://127.0.0.1:389",
readMeta.getProperties().get("ldap.server"));
+ Assertions.assertEquals("comment", readMeta.getComment());
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
new file mode 100644
index 00000000000..2a01a237209
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
@@ -0,0 +1,111 @@
+// 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.nereids.parser;
+
+import org.apache.doris.nereids.exceptions.ParseException;
+import
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
+import
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
+import
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AuthenticationIntegrationParserTest {
+
+ private final NereidsParser parser = new NereidsParser();
+
+ @Test
+ public void testCreateAuthenticationIntegrationParse() {
+ LogicalPlan plan = parser.parseSingle("CREATE AUTHENTICATION
INTEGRATION IF NOT EXISTS corp_ldap "
+ + "PROPERTIES ('type'='ldap',
'ldap.server'='ldap://127.0.0.1:389') "
+ + "COMMENT 'ldap integration'");
+
+
Assertions.assertInstanceOf(CreateAuthenticationIntegrationCommand.class, plan);
+ CreateAuthenticationIntegrationCommand command =
(CreateAuthenticationIntegrationCommand) plan;
+ Assertions.assertEquals("corp_ldap", command.getIntegrationName());
+ Assertions.assertTrue(command.isSetIfNotExists());
+ Assertions.assertEquals("ldap", command.getProperties().get("type"));
+ Assertions.assertEquals("ldap://127.0.0.1:389",
command.getProperties().get("ldap.server"));
+ Assertions.assertEquals("ldap integration", command.getComment());
+ }
+
+ @Test
+ public void testCreateAuthenticationIntegrationRequireType() {
+ Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+ "CREATE AUTHENTICATION INTEGRATION corp_ldap "
+ + "PROPERTIES
('ldap.server'='ldap://127.0.0.1:389')"));
+ }
+
+ @Test
+ public void testAlterAuthenticationIntegrationParse() {
+ LogicalPlan alterProperties = parser.parseSingle("ALTER AUTHENTICATION
INTEGRATION corp_ldap "
+ + "SET PROPERTIES ('ldap.server'='ldap://127.0.0.1:1389')");
+
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class,
alterProperties);
+
+ AlterAuthenticationIntegrationCommand alterPropertiesCommand =
+ (AlterAuthenticationIntegrationCommand) alterProperties;
+ Assertions.assertEquals("corp_ldap",
alterPropertiesCommand.getIntegrationName());
+
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.SET_PROPERTIES,
+ alterPropertiesCommand.getAlterType());
+ Assertions.assertEquals("ldap://127.0.0.1:1389",
+ alterPropertiesCommand.getProperties().get("ldap.server"));
+
+ LogicalPlan unsetProperties = parser.parseSingle("ALTER AUTHENTICATION
INTEGRATION corp_ldap "
+ + "UNSET PROPERTIES ('ldap.server')");
+
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class,
unsetProperties);
+
+ AlterAuthenticationIntegrationCommand unsetPropertiesCommand =
+ (AlterAuthenticationIntegrationCommand) unsetProperties;
+
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.UNSET_PROPERTIES,
+ unsetPropertiesCommand.getAlterType());
+
Assertions.assertTrue(unsetPropertiesCommand.getUnsetProperties().contains("ldap.server"));
+
+ LogicalPlan alterComment = parser.parseSingle(
+ "ALTER AUTHENTICATION INTEGRATION corp_ldap SET COMMENT 'new
comment'");
+
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class,
alterComment);
+
+ AlterAuthenticationIntegrationCommand alterCommentCommand =
+ (AlterAuthenticationIntegrationCommand) alterComment;
+
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.SET_COMMENT,
+ alterCommentCommand.getAlterType());
+ Assertions.assertEquals("new comment",
alterCommentCommand.getComment());
+ }
+
+ @Test
+ public void testAlterAuthenticationIntegrationRejectType() {
+ Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+ "ALTER AUTHENTICATION INTEGRATION corp_ldap SET PROPERTIES
('TYPE'='oidc')"));
+ Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+ "ALTER AUTHENTICATION INTEGRATION corp_ldap UNSET PROPERTIES
('TYPE')"));
+ }
+
+ @Test
+ public void testDropAuthenticationIntegrationParse() {
+ LogicalPlan plan1 = parser.parseSingle("DROP AUTHENTICATION
INTEGRATION corp_ldap");
+
Assertions.assertInstanceOf(DropAuthenticationIntegrationCommand.class, plan1);
+ DropAuthenticationIntegrationCommand drop1 =
(DropAuthenticationIntegrationCommand) plan1;
+ Assertions.assertEquals("corp_ldap", drop1.getIntegrationName());
+ Assertions.assertFalse(drop1.isIfExists());
+
+ LogicalPlan plan2 = parser.parseSingle("DROP AUTHENTICATION
INTEGRATION IF EXISTS corp_ldap");
+
Assertions.assertInstanceOf(DropAuthenticationIntegrationCommand.class, plan2);
+ DropAuthenticationIntegrationCommand drop2 =
(DropAuthenticationIntegrationCommand) plan2;
+ Assertions.assertTrue(drop2.isIfExists());
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
new file mode 100644
index 00000000000..ed1a70b03d5
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
@@ -0,0 +1,238 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.authentication.AuthenticationIntegrationMgr;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationCommandTest {
+
+ @Mocked
+ private Env env;
+
+ @Mocked
+ private AccessControllerManager accessManager;
+
+ @Mocked
+ private AuthenticationIntegrationMgr authenticationIntegrationMgr;
+
+ @Mocked
+ private ConnectContext connectContext;
+
+ private static Map<String, String> map(String... kvs) {
+ Map<String, String> result = new LinkedHashMap<>();
+ for (int i = 0; i < kvs.length; i += 2) {
+ result.put(kvs[i], kvs[i + 1]);
+ }
+ return result;
+ }
+
+ private static Set<String> set(String... keys) {
+ Set<String> result = new LinkedHashSet<>();
+ Collections.addAll(result, keys);
+ return result;
+ }
+
+ @Test
+ public void testCreateCommandRunAndDenied() throws Exception {
+ CreateAuthenticationIntegrationCommand createCommand =
+ new CreateAuthenticationIntegrationCommand("corp_ldap", false,
+ map("type", "ldap", "ldap.server",
"ldap://127.0.0.1:389"), "comment");
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ result = true;
+
+ env.getAuthenticationIntegrationMgr();
+ minTimes = 0;
+ result = authenticationIntegrationMgr;
+
+ authenticationIntegrationMgr.createAuthenticationIntegration(
+ anyString, anyBoolean, (Map<String, String>) any,
anyString);
+ times = 1;
+ }
+ };
+
+ Assertions.assertDoesNotThrow(() -> createCommand.run(connectContext,
null));
+ Assertions.assertEquals(StmtType.CREATE, createCommand.stmtType());
+ Assertions.assertTrue(createCommand.needAuditEncryption());
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ result = false;
+ }
+ };
+
+ Assertions.assertThrows(AnalysisException.class, () ->
createCommand.run(connectContext, null));
+ }
+
+ @Test
+ public void testAlterCommandRun() throws Exception {
+ AlterAuthenticationIntegrationCommand setPropertiesCommand =
+ AlterAuthenticationIntegrationCommand.forSetProperties(
+ "corp_ldap", map("ldap.server",
"ldap://127.0.0.1:1389"));
+ AlterAuthenticationIntegrationCommand unsetPropertiesCommand =
+ AlterAuthenticationIntegrationCommand.forUnsetProperties(
+ "corp_ldap", set("ldap.server"));
+ AlterAuthenticationIntegrationCommand setCommentCommand =
+
AlterAuthenticationIntegrationCommand.forSetComment("corp_ldap", "new comment");
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ minTimes = 0;
+ result = true;
+
+ env.getAuthenticationIntegrationMgr();
+ minTimes = 0;
+ result = authenticationIntegrationMgr;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationProperties(
+ anyString, (Map<String, String>) any);
+ times = 1;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationUnsetProperties(
+ anyString, (Set<String>) any);
+ times = 1;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationComment(anyString,
anyString);
+ times = 1;
+ }
+ };
+
+ Assertions.assertDoesNotThrow(() ->
setPropertiesCommand.doRun(connectContext, null));
+ Assertions.assertDoesNotThrow(() ->
unsetPropertiesCommand.doRun(connectContext, null));
+ Assertions.assertDoesNotThrow(() ->
setCommentCommand.doRun(connectContext, null));
+ Assertions.assertTrue(setPropertiesCommand.needAuditEncryption());
+ Assertions.assertTrue(unsetPropertiesCommand.needAuditEncryption());
+ Assertions.assertTrue(setCommentCommand.needAuditEncryption());
+ }
+
+ @Test
+ public void testAlterCommandDenied() {
+ AlterAuthenticationIntegrationCommand setPropertiesCommand =
+ AlterAuthenticationIntegrationCommand.forSetProperties(
+ "corp_ldap", map("ldap.server",
"ldap://127.0.0.1:1389"));
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ result = false;
+ }
+ };
+
+ Assertions.assertThrows(AnalysisException.class, () ->
setPropertiesCommand.doRun(connectContext, null));
+ }
+
+ @Test
+ public void testDropCommandRunAndDenied() throws Exception {
+ DropAuthenticationIntegrationCommand dropCommand =
+ new DropAuthenticationIntegrationCommand(true, "corp_ldap");
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ result = true;
+
+ env.getAuthenticationIntegrationMgr();
+ minTimes = 0;
+ result = authenticationIntegrationMgr;
+
+
authenticationIntegrationMgr.dropAuthenticationIntegration(anyString,
anyBoolean);
+ times = 1;
+ }
+ };
+
+ Assertions.assertDoesNotThrow(() -> dropCommand.doRun(connectContext,
null));
+
+ new Expectations() {
+ {
+ Env.getCurrentEnv();
+ minTimes = 0;
+ result = env;
+
+ env.getAccessManager();
+ minTimes = 0;
+ result = accessManager;
+
+ accessManager.checkGlobalPriv((ConnectContext) any,
PrivPredicate.ADMIN);
+ result = false;
+ }
+ };
+
+ Assertions.assertThrows(AnalysisException.class, () ->
dropCommand.doRun(connectContext, null));
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
b/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
new file mode 100644
index 00000000000..042a7fc40d2
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
@@ -0,0 +1,47 @@
+// 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.persist;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class DropAuthenticationIntegrationOperationLogTest {
+
+ @Test
+ public void testWriteReadRoundTrip() throws Exception {
+ DropAuthenticationIntegrationOperationLog log =
+ new DropAuthenticationIntegrationOperationLog("corp_ldap");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (DataOutputStream dos = new DataOutputStream(bos)) {
+ log.write(dos);
+ }
+
+ DropAuthenticationIntegrationOperationLog read;
+ try (DataInputStream dis = new DataInputStream(new
ByteArrayInputStream(bos.toByteArray()))) {
+ read = DropAuthenticationIntegrationOperationLog.read(dis);
+ }
+
+ Assertions.assertEquals("corp_ldap", read.getIntegrationName());
+ }
+}
diff --git
a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
new file mode 100644
index 00000000000..c65ac577574
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
@@ -0,0 +1,78 @@
+// 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.
+
+suite("test_authentication_integration_auth", "p0,auth") {
+ String suiteName = "test_authentication_integration_auth"
+ String integrationName = "${suiteName}_ldap"
+
+ try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+
+ try {
+ test {
+ sql """
+ CREATE AUTHENTICATION INTEGRATION ${integrationName}
+ PROPERTIES ('ldap.server'='ldap://127.0.0.1:389')
+ """
+ exception "Property 'type' is required"
+ }
+
+ sql """
+ CREATE AUTHENTICATION INTEGRATION ${integrationName}
+ PROPERTIES (
+ 'type'='ldap',
+ 'ldap.server'='ldap://127.0.0.1:389',
+ 'ldap.admin_password'='123456'
+ )
+ COMMENT 'for regression test'
+ """
+
+ test {
+ sql """
+ CREATE AUTHENTICATION INTEGRATION ${integrationName}
+ PROPERTIES ('type'='ldap',
'ldap.server'='ldap://127.0.0.1:1389')
+ """
+ exception "already exists"
+ }
+
+ test {
+ sql """
+ ALTER AUTHENTICATION INTEGRATION ${integrationName}
+ SET PROPERTIES ('type'='oidc')
+ """
+ exception "does not allow modifying property 'type'"
+ }
+
+ sql """
+ ALTER AUTHENTICATION INTEGRATION ${integrationName}
+ SET PROPERTIES (
+ 'ldap.server'='ldap://127.0.0.1:1389',
+ 'ldap.admin_password'='abcdef'
+ )
+ """
+
+ sql """ALTER AUTHENTICATION INTEGRATION ${integrationName} SET COMMENT
'updated comment'"""
+
+ test {
+ sql """DROP AUTHENTICATION INTEGRATION
${integrationName}_not_exist"""
+ exception "does not exist"
+ }
+
+ sql """DROP AUTHENTICATION INTEGRATION IF EXISTS
${integrationName}_not_exist"""
+ } finally {
+ try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]