This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new f0cc411202d branch-4.1: [feat](auth) support querying authentication
integrations from system table (#61246 #61172 #60902) (#61517)
f0cc411202d is described below
commit f0cc411202d8a46bae69056db2fe21c7480ef63c
Author: Calvin Kirs <[email protected]>
AuthorDate: Thu Mar 19 21:46:51 2026 +0800
branch-4.1: [feat](auth) support querying authentication integrations from
system table (#61246 #61172 #60902) (#61517)
[feat](auth) support querying authentication integrations from system
table (#61246 #61172 #60902)
---
be/src/exec/schema_scanner.cpp | 3 +
.../schema_authentication_integrations_scanner.cpp | 150 +++++++++++++
.../schema_authentication_integrations_scanner.h | 55 +++++
.../antlr4/org/apache/doris/nereids/DorisLexer.g4 | 2 +
.../antlr4/org/apache/doris/nereids/DorisParser.g4 | 15 ++
.../org/apache/doris/analysis/SchemaTableType.java | 4 +-
.../AuthenticationIntegrationMeta.java | 178 +++++++++++++++
.../AuthenticationIntegrationMgr.java | 206 +++++++++++++++++
.../main/java/org/apache/doris/catalog/Env.java | 27 ++-
.../java/org/apache/doris/catalog/SchemaTable.java | 11 +
.../org/apache/doris/common/UserAuditMetadata.java | 77 +++++++
.../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 | 136 +++++++++++
.../CreateAuthenticationIntegrationCommand.java | 95 ++++++++
.../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 +-
.../doris/tablefunction/MetadataGenerator.java | 96 ++++++++
.../AuthenticationIntegrationMetaTest.java | 190 ++++++++++++++++
.../AuthenticationIntegrationMgrTest.java | 216 ++++++++++++++++++
.../org/apache/doris/catalog/SchemaTableTest.java | 10 +
.../AuthenticationIntegrationParserTest.java | 111 +++++++++
.../AuthenticationIntegrationCommandTest.java | 249 +++++++++++++++++++++
...pAuthenticationIntegrationOperationLogTest.java | 47 ++++
.../doris/service/FrontendServiceImplTest.java | 56 +++++
gensrc/thrift/Descriptors.thrift | 1 +
gensrc/thrift/FrontendService.thrift | 1 +
.../test_authentication_integration_auth.groovy | 109 +++++++++
35 files changed, 2344 insertions(+), 4 deletions(-)
diff --git a/be/src/exec/schema_scanner.cpp b/be/src/exec/schema_scanner.cpp
index ff7ee7e02ae..6f54ba591e1 100644
--- a/be/src/exec/schema_scanner.cpp
+++ b/be/src/exec/schema_scanner.cpp
@@ -27,6 +27,7 @@
#include <utility>
#include "exec/schema_scanner/schema_active_queries_scanner.h"
+#include "exec/schema_scanner/schema_authentication_integrations_scanner.h"
#include "exec/schema_scanner/schema_backend_active_tasks.h"
#include "exec/schema_scanner/schema_backend_configuration_scanner.h"
#include "exec/schema_scanner/schema_backend_kerberos_ticket_cache.h"
@@ -259,6 +260,8 @@ std::unique_ptr<SchemaScanner>
SchemaScanner::create(TSchemaTableType::type type
return SchemaClusterSnapshotPropertiesScanner::create_unique();
case TSchemaTableType::SCH_COLUMN_DATA_SIZES:
return SchemaColumnDataSizesScanner::create_unique();
+ case TSchemaTableType::SCH_AUTHENTICATION_INTEGRATIONS:
+ return SchemaAuthenticationIntegrationsScanner::create_unique();
default:
return SchemaDummyScanner::create_unique();
break;
diff --git
a/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp
b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp
new file mode 100644
index 00000000000..7367b3aa99a
--- /dev/null
+++ b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp
@@ -0,0 +1,150 @@
+// 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.
+
+#include "exec/schema_scanner/schema_authentication_integrations_scanner.h"
+
+#include <utility>
+
+#include "exec/schema_scanner/schema_helper.h"
+#include "runtime/client_cache.h"
+#include "runtime/exec_env.h"
+#include "runtime/runtime_state.h"
+#include "util/thrift_rpc_helper.h"
+#include "vec/common/string_ref.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type_factory.hpp"
+
+namespace doris {
+#include "common/compile_check_begin.h"
+
+std::vector<SchemaScanner::ColumnDesc>
SchemaAuthenticationIntegrationsScanner::_s_tbls_columns = {
+ {"NAME", TYPE_VARCHAR, sizeof(StringRef), true},
+ {"TYPE", TYPE_VARCHAR, sizeof(StringRef), true},
+ {"PROPERTIES", TYPE_STRING, sizeof(StringRef), true},
+ {"COMMENT", TYPE_STRING, sizeof(StringRef), true},
+ {"CREATE_USER", TYPE_STRING, sizeof(StringRef), true},
+ {"CREATE_TIME", TYPE_STRING, sizeof(StringRef), true},
+ {"ALTER_USER", TYPE_STRING, sizeof(StringRef), true},
+ {"MODIFY_TIME", TYPE_STRING, sizeof(StringRef), true},
+};
+
+SchemaAuthenticationIntegrationsScanner::SchemaAuthenticationIntegrationsScanner()
+ : SchemaScanner(_s_tbls_columns,
TSchemaTableType::SCH_AUTHENTICATION_INTEGRATIONS) {}
+
+Status SchemaAuthenticationIntegrationsScanner::start(RuntimeState* state) {
+ if (!_is_init) {
+ return Status::InternalError("used before initialized.");
+ }
+ _block_rows_limit = state->batch_size();
+ _rpc_timeout_ms = state->execution_timeout() * 1000;
+ return Status::OK();
+}
+
+Status
SchemaAuthenticationIntegrationsScanner::_get_authentication_integrations_block_from_fe()
{
+ TNetworkAddress master_addr =
ExecEnv::GetInstance()->cluster_info()->master_fe_addr;
+
+ TSchemaTableRequestParams schema_table_request_params;
+ for (int i = 0; i < _s_tbls_columns.size(); i++) {
+ schema_table_request_params.__isset.columns_name = true;
+
schema_table_request_params.columns_name.emplace_back(_s_tbls_columns[i].name);
+ }
+
schema_table_request_params.__set_current_user_ident(*_param->common_param->current_user_ident);
+ if (_param->common_param->frontend_conjuncts) {
+ schema_table_request_params.__set_frontend_conjuncts(
+ *_param->common_param->frontend_conjuncts);
+ }
+
+ TFetchSchemaTableDataRequest request;
+
request.__set_schema_table_name(TSchemaTableName::AUTHENTICATION_INTEGRATIONS);
+ request.__set_schema_table_params(schema_table_request_params);
+
+ TFetchSchemaTableDataResult result;
+ RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>(
+ master_addr.hostname, master_addr.port,
+ [&request, &result](FrontendServiceConnection& client) {
+ client->fetchSchemaTableData(result, request);
+ },
+ _rpc_timeout_ms));
+
+ Status status(Status::create(result.status));
+ if (!status.ok()) {
+ LOG(WARNING) << "fetch authentication integrations from FE failed,
errmsg=" << status;
+ return status;
+ }
+
+ _authentication_integrations_block = vectorized::Block::create_unique();
+ for (int i = 0; i < _s_tbls_columns.size(); ++i) {
+ auto data_type =
vectorized::DataTypeFactory::instance().create_data_type(
+ _s_tbls_columns[i].type, true);
+
_authentication_integrations_block->insert(vectorized::ColumnWithTypeAndName(
+ data_type->create_column(), data_type,
_s_tbls_columns[i].name));
+ }
+ _authentication_integrations_block->reserve(_block_rows_limit);
+
+ std::vector<TRow> result_data = std::move(result.data_batch);
+ if (!result_data.empty()) {
+ auto col_size = result_data[0].column_value.size();
+ if (col_size != _s_tbls_columns.size()) {
+ return Status::InternalError<false>(
+ "authentication integrations schema is not match for FE
and BE");
+ }
+ }
+
+ for (int i = 0; i < result_data.size(); i++) {
+ const TRow& row = result_data[i];
+ for (int j = 0; j < _s_tbls_columns.size(); j++) {
+ RETURN_IF_ERROR(insert_block_column(row.column_value[j], j,
+
_authentication_integrations_block.get(),
+ _s_tbls_columns[j].type));
+ }
+ }
+ return Status::OK();
+}
+
+Status
SchemaAuthenticationIntegrationsScanner::get_next_block_internal(vectorized::Block*
block,
+ bool*
eos) {
+ if (!_is_init) {
+ return Status::InternalError("Used before initialized.");
+ }
+
+ if (nullptr == block || nullptr == eos) {
+ return Status::InternalError("input pointer is nullptr.");
+ }
+
+ if (_authentication_integrations_block == nullptr) {
+ RETURN_IF_ERROR(_get_authentication_integrations_block_from_fe());
+ _total_rows =
static_cast<int>(_authentication_integrations_block->rows());
+ }
+
+ if (_row_idx == _total_rows) {
+ *eos = true;
+ return Status::OK();
+ }
+
+ int current_batch_rows = std::min(_block_rows_limit, _total_rows -
_row_idx);
+ vectorized::MutableBlock mblock =
vectorized::MutableBlock::build_mutable_block(block);
+ RETURN_IF_ERROR(mblock.add_rows(_authentication_integrations_block.get(),
_row_idx,
+ current_batch_rows));
+ _row_idx += current_batch_rows;
+
+ *eos = _row_idx == _total_rows;
+ return Status::OK();
+}
+
+#include "common/compile_check_end.h"
+} // namespace doris
diff --git
a/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h
b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h
new file mode 100644
index 00000000000..5563ac21f3e
--- /dev/null
+++ b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <gen_cpp/FrontendService_types.h>
+
+#include <vector>
+
+#include "common/status.h"
+#include "exec/schema_scanner.h"
+
+namespace doris {
+class RuntimeState;
+namespace vectorized {
+class Block;
+} // namespace vectorized
+
+class SchemaAuthenticationIntegrationsScanner : public SchemaScanner {
+ ENABLE_FACTORY_CREATOR(SchemaAuthenticationIntegrationsScanner);
+
+public:
+ SchemaAuthenticationIntegrationsScanner();
+ ~SchemaAuthenticationIntegrationsScanner() override = default;
+
+ Status start(RuntimeState* state) override;
+ Status get_next_block_internal(vectorized::Block* block, bool* eos)
override;
+
+ static std::vector<SchemaScanner::ColumnDesc> _s_tbls_columns;
+
+private:
+ Status _get_authentication_integrations_block_from_fe();
+
+ int _block_rows_limit = 4096;
+ int _row_idx = 0;
+ int _total_rows = 0;
+ int _rpc_timeout_ms = 3000;
+ std::unique_ptr<vectorized::Block> _authentication_integrations_block =
nullptr;
+};
+
+} // namespace doris
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 81d36337d94..47ee4eea108 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 @@ ASC: 'ASC';
ASOF: 'ASOF';
AT: 'AT';
AUTHORS: 'AUTHORS';
+AUTHENTICATION: 'AUTHENTICATION';
AUTO: 'AUTO';
AUTO_INCREMENT: 'AUTO_INCREMENT';
ALWAYS: 'ALWAYS';
@@ -296,6 +297,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 23ab160196c..32cdb65a71e 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
@@ -212,6 +212,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
@@ -295,6 +297,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
@@ -338,6 +346,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
@@ -1477,6 +1486,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)*
;
@@ -1973,6 +1986,7 @@ nonReserved
| ARRAY
| AT
| AUTHORS
+ | AUTHENTICATION
| AUTO_INCREMENT
| BACKENDS
| BACKUP
@@ -2126,6 +2140,7 @@ nonReserved
| IGNORE
| IMMEDIATE
| INCREMENTAL
+ | INTEGRATION
| INDEXES
| INSERT
| INVERTED
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
index 5576c6d294b..c5beb18953c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
@@ -110,7 +110,9 @@ public enum SchemaTableType {
SCH_BLACKHOLE("BLACKHOLE", "BLACKHOLE",
TSchemaTableType.SCH_BLACKHOLE),
SCH_COLUMN_DATA_SIZES("COLUMN_DATA_SIZES", "COLUMN_DATA_SIZES",
- TSchemaTableType.SCH_COLUMN_DATA_SIZES);
+ TSchemaTableType.SCH_COLUMN_DATA_SIZES),
+ SCH_AUTHENTICATION_INTEGRATIONS("AUTHENTICATION_INTEGRATIONS",
"AUTHENTICATION_INTEGRATIONS",
+ TSchemaTableType.SCH_AUTHENTICATION_INTEGRATIONS);
private static final String dbName = "INFORMATION_SCHEMA";
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..a57e3d3ced4
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
@@ -0,0 +1,178 @@
+// 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.UserAuditMetadata;
+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 extends UserAuditMetadata
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() {
+ super();
+ this.name = "";
+ this.type = "";
+ this.properties = Collections.emptyMap();
+ this.comment = null;
+ }
+
+ public AuthenticationIntegrationMeta(String name, String type, Map<String,
String> properties, String comment,
+ String createUser, long createTime, String alterUser, long
modifyTime) {
+ super(createUser, createTime, alterUser, modifyTime);
+ 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, String createUser)
+ 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");
+ }
+ long currentTime = System.currentTimeMillis();
+ return new AuthenticationIntegrationMeta(integrationName, type,
copiedProperties, comment,
+ Objects.requireNonNull(createUser, "createUser can not be
null"),
+ currentTime, createUser, currentTime);
+ }
+
+ /**
+ * Build a new metadata object after ALTER ... SET PROPERTIES.
+ */
+ public AuthenticationIntegrationMeta withAlterProperties(Map<String,
String> propertiesDelta, String alterUser)
+ 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,
+ getCreateUser(), getCreateTime(),
+ Objects.requireNonNull(alterUser, "alterUser can not be
null"), System.currentTimeMillis());
+ }
+
+ /**
+ * Build a new metadata object after ALTER ... UNSET PROPERTIES.
+ */
+ public AuthenticationIntegrationMeta withUnsetProperties(Set<String>
propertiesToUnset, String alterUser)
+ 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,
+ getCreateUser(), getCreateTime(),
+ Objects.requireNonNull(alterUser, "alterUser can not be
null"), System.currentTimeMillis());
+ }
+
+ public AuthenticationIntegrationMeta withComment(String newComment, String
alterUser) {
+ return new AuthenticationIntegrationMeta(name, type, properties,
newComment,
+ getCreateUser(), getCreateTime(),
+ Objects.requireNonNull(alterUser, "alterUser can not be
null"), System.currentTimeMillis());
+ }
+
+ 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..487637056b8
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.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.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,
+ String createUser)
+ throws DdlException {
+ AuthenticationIntegrationMeta meta =
+ AuthenticationIntegrationMeta.fromCreateSql(integrationName,
properties, comment, createUser);
+ 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, String
alterUser) throws DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withAlterProperties(properties, alterUser);
+ 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, String
alterUser) throws DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withUnsetProperties(propertiesToUnset, alterUser);
+ 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, String alterUser)
+ throws DdlException {
+ writeLock();
+ try {
+ AuthenticationIntegrationMeta current =
getOrThrow(integrationName);
+ AuthenticationIntegrationMeta updated =
current.withComment(comment, alterUser);
+ 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 377bedf1eed..961da675754 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
@@ -37,6 +37,7 @@ import org.apache.doris.analysis.ReplacePartitionClause;
import org.apache.doris.analysis.RollupRenameClause;
import org.apache.doris.analysis.SlotRef;
import org.apache.doris.analysis.TableRenameClause;
+import org.apache.doris.authentication.AuthenticationIntegrationMgr;
import org.apache.doris.backup.BackupHandler;
import org.apache.doris.backup.RestoreJob;
import org.apache.doris.binlog.BinlogGcer;
@@ -374,6 +375,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;
@@ -705,6 +707,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();
@@ -2471,6 +2474,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.
**/
@@ -2784,6 +2798,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;
@@ -5133,6 +5155,10 @@ public class Env {
return sqlBlockRuleMgr;
}
+ public AuthenticationIntegrationMgr getAuthenticationIntegrationMgr() {
+ return authenticationIntegrationMgr;
+ }
+
public RoutineLoadTaskScheduler getRoutineLoadTaskScheduler() {
return routineLoadTaskScheduler;
}
@@ -7438,4 +7464,3 @@ public class Env {
protected void cloneClusterSnapshot() throws Exception {}
}
-
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
index edebeff57d1..17003808040 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
@@ -792,6 +792,17 @@ public class SchemaTable extends Table {
.column("MAX_RESERVED_SNAPSHOTS",
ScalarType.createType(PrimitiveType.BIGINT))
.column("SNAPSHOT_INTERVAL_SECONDS",
ScalarType.createType(PrimitiveType.BIGINT))
.build()))
+ .put("authentication_integrations",
+ new SchemaTable(SystemIdGenerator.getNextId(),
"authentication_integrations", TableType.SCHEMA,
+ builder().column("NAME", ScalarType.createVarchar(256))
+ .column("TYPE", ScalarType.createVarchar(64))
+ .column("PROPERTIES",
ScalarType.createStringType())
+ .column("COMMENT", ScalarType.createStringType())
+ .column("CREATE_USER",
ScalarType.createStringType())
+ .column("CREATE_TIME",
ScalarType.createStringType())
+ .column("ALTER_USER",
ScalarType.createStringType())
+ .column("MODIFY_TIME",
ScalarType.createStringType())
+ .build()))
.build();
private boolean fetchAllFe = false;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java
b/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java
new file mode 100644
index 00000000000..d473068eeb8
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java
@@ -0,0 +1,77 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.common;
+
+import org.apache.doris.common.util.TimeUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+/**
+ * Common persisted audit metadata for user-driven DDL objects.
+ * Time is stored as epoch millis and formatted lazily with Doris/session
timezone when needed.
+ */
+public abstract class UserAuditMetadata {
+ @SerializedName(value = "cu")
+ private String createUser;
+ @SerializedName(value = "ct")
+ private long createTime;
+ @SerializedName(value = "au")
+ private String alterUser;
+ @SerializedName(value = "mt")
+ private long modifyTime;
+
+ protected UserAuditMetadata() {
+ }
+
+ protected UserAuditMetadata(String createUser, long createTime, String
alterUser, long modifyTime) {
+ this.createUser = Objects.requireNonNull(createUser, "createUser can
not be null");
+ this.alterUser = Objects.requireNonNull(alterUser, "alterUser can not
be null");
+ Preconditions.checkArgument(createTime > 0, "createTime must be
positive");
+ Preconditions.checkArgument(modifyTime > 0, "modifyTime must be
positive");
+ Preconditions.checkArgument(modifyTime >= createTime, "modifyTime can
not be earlier than createTime");
+ this.createTime = createTime;
+ this.modifyTime = modifyTime;
+ }
+
+ public String getCreateUser() {
+ return createUser;
+ }
+
+ public long getCreateTime() {
+ return createTime;
+ }
+
+ public String getAlterUser() {
+ return alterUser;
+ }
+
+ public long getModifyTime() {
+ return modifyTime;
+ }
+
+ public String getCreateTimeString() {
+ return TimeUtils.longToTimeString(createTime);
+ }
+
+ public String getModifyTimeString() {
+ return TimeUtils.longToTimeString(modifyTime);
+ }
+}
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 cb6dee000de..5b36fcb98ee 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;
@@ -710,6 +712,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 7d4d972860e..dde35800b85 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
@@ -643,6 +643,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;
@@ -684,6 +685,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;
@@ -716,6 +718,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;
@@ -2129,6 +2132,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;
@@ -5081,6 +5096,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;
@@ -7133,6 +7157,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();
@@ -7169,6 +7206,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);
@@ -7287,6 +7356,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 9271e7354d5..3c0ee3371a0 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
@@ -152,6 +152,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,
@@ -180,10 +181,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..0a8ef9c1868
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
@@ -0,0 +1,136 @@
+// 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(ctx,
PrivPredicate.ADMIN)) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ADMIN");
+ }
+ String alterUser = Objects.requireNonNull(ctx.getQualifiedUser(),
"qualifiedUser can not be null");
+ switch (alterType) {
+ case SET_PROPERTIES:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationProperties(integrationName, properties,
alterUser);
+ return;
+ case UNSET_PROPERTIES:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationUnsetProperties(integrationName,
unsetProperties, alterUser);
+ return;
+ case SET_COMMENT:
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+
.alterAuthenticationIntegrationComment(integrationName, comment, alterUser);
+ 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..690a024086c
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
@@ -0,0 +1,95 @@
+// 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(ctx,
PrivPredicate.ADMIN)) {
+
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"ADMIN");
+ }
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+ .createAuthenticationIntegration(integrationName, ifNotExists,
properties, comment,
+ Objects.requireNonNull(ctx.getQualifiedUser(),
"qualifiedUser can not be null"));
+ }
+
+ @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 7ab5334529b..d2f645be59b 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;
@@ -104,6 +106,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;
@@ -531,6 +534,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);
}
@@ -563,6 +571,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);
}
@@ -878,6 +891,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 95885f92286..4a2754d0b12 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;
@@ -1072,6 +1073,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);
@@ -2279,6 +2296,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 fc42ce65163..d1964d9d769 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
@@ -416,6 +416,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/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
index 62e9768d614..878e7452b24 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
@@ -18,6 +18,7 @@
package org.apache.doris.tablefunction;
import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
import org.apache.doris.blockrule.SqlBlockRule;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DataProperty;
@@ -49,6 +50,7 @@ import org.apache.doris.common.proc.PartitionsProcDir;
import org.apache.doris.common.profile.RuntimeProfile;
import org.apache.doris.common.util.DebugUtil;
import org.apache.doris.common.util.NetUtils;
+import org.apache.doris.common.util.PrintableMap;
import org.apache.doris.common.util.TimeUtils;
import org.apache.doris.common.util.Util;
import org.apache.doris.datasource.CatalogIf;
@@ -131,7 +133,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@@ -161,6 +165,8 @@ public class MetadataGenerator {
private static final ImmutableMap<String, Integer>
SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX;
+ private static final ImmutableMap<String, Integer>
AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
+
static {
ImmutableMap.Builder<String, Integer> activeQueriesbuilder = new
ImmutableMap.Builder();
List<Column> activeQueriesColList =
SchemaTable.TABLE_MAP.get("active_queries").getFullSchema();
@@ -237,6 +243,15 @@ public class MetadataGenerator {
sqlBlockRuleStatusBuilder.put(sqlBlockRuleStatusBuilderColList.get(i).getName().toLowerCase(),
i);
}
SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX =
sqlBlockRuleStatusBuilder.build();
+
+ ImmutableMap.Builder<String, Integer>
authenticationIntegrationsBuilder = new ImmutableMap.Builder();
+ List<Column> authenticationIntegrationsColList =
SchemaTable.TABLE_MAP.get("authentication_integrations")
+ .getFullSchema();
+ for (int i = 0; i < authenticationIntegrationsColList.size(); i++) {
+ authenticationIntegrationsBuilder.put(
+
authenticationIntegrationsColList.get(i).getName().toLowerCase(), i);
+ }
+ AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX =
authenticationIntegrationsBuilder.build();
}
public static TFetchSchemaTableDataResult
getMetadataTable(TFetchSchemaTableDataRequest request) throws TException {
@@ -351,6 +366,10 @@ public class MetadataGenerator {
result = sqlBlockRuleStatusMetadataResult(schemaTableParams);
columnIndex = SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX;
break;
+ case AUTHENTICATION_INTEGRATIONS:
+ result =
authenticationIntegrationsMetadataResult(schemaTableParams);
+ columnIndex = AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
+ break;
default:
return errorResult("invalid schema table name.");
}
@@ -691,6 +710,83 @@ public class MetadataGenerator {
return result;
}
+ private static TFetchSchemaTableDataResult
authenticationIntegrationsMetadataResult(
+ TSchemaTableRequestParams params) {
+ if (!params.isSetCurrentUserIdent()) {
+ return errorResult("current user ident is not set.");
+ }
+ UserIdentity currentUserIdentity =
UserIdentity.fromThrift(params.getCurrentUserIdent());
+ TFetchSchemaTableDataResult result = new TFetchSchemaTableDataResult();
+ List<TRow> dataBatch = Lists.newArrayList();
+ result.setDataBatch(dataBatch);
+ result.setStatus(new TStatus(TStatusCode.OK));
+ if
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(currentUserIdentity,
PrivPredicate.ADMIN)) {
+ return result;
+ }
+
+ List<Expression> conjuncts = Collections.EMPTY_LIST;
+ if (params.isSetFrontendConjuncts()) {
+ conjuncts =
FrontendConjunctsUtils.convertToExpression(params.getFrontendConjuncts());
+ }
+ List<Expression> nameConjuncts =
FrontendConjunctsUtils.filterBySlotName(conjuncts, "NAME");
+ List<Expression> typeConjuncts =
FrontendConjunctsUtils.filterBySlotName(conjuncts, "TYPE");
+
+ for (AuthenticationIntegrationMeta meta :
Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+ .getAuthenticationIntegrations().values()) {
+ if (FrontendConjunctsUtils.isFiltered(nameConjuncts, "NAME",
meta.getName())
+ || FrontendConjunctsUtils.isFiltered(typeConjuncts,
"TYPE", meta.getType())) {
+ continue;
+ }
+ TRow row = new TRow();
+ row.addToColumnValue(new TCell().setStringVal(meta.getName()));
+ row.addToColumnValue(new TCell().setStringVal(meta.getType()));
+ row.addToColumnValue(new
TCell().setStringVal(maskAuthenticationProperties(meta.getProperties())));
+ if (meta.getComment() == null) {
+ row.addToColumnValue(new TCell());
+ } else {
+ row.addToColumnValue(new
TCell().setStringVal(meta.getComment()));
+ }
+ row.addToColumnValue(new
TCell().setStringVal(meta.getCreateUser()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getCreateTimeString()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getAlterUser()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getModifyTimeString()));
+ dataBatch.add(row);
+ }
+ return result;
+ }
+
+ private static String maskAuthenticationProperties(Map<String, String>
properties) {
+ Map<String, String> maskedProperties = new LinkedHashMap<>();
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ if (shouldMaskAuthenticationProperty(entry.getKey())) {
+ maskedProperties.put(entry.getKey(),
PrintableMap.PASSWORD_MASK);
+ } else {
+ maskedProperties.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return new PrintableMap<>(maskedProperties, "=", true,
false).toString();
+ }
+
+ private static boolean shouldMaskAuthenticationProperty(String key) {
+ String lowerCaseKey = key.toLowerCase(Locale.ROOT);
+ return PrintableMap.SENSITIVE_KEY.contains(key)
+ || lowerCaseKey.startsWith("secret.")
+ || lowerCaseKey.endsWith(".password")
+ || lowerCaseKey.endsWith("_password")
+ || lowerCaseKey.equals("secret")
+ || lowerCaseKey.endsWith(".secret")
+ || lowerCaseKey.endsWith("_secret")
+ || lowerCaseKey.endsWith(".secret_key")
+ || lowerCaseKey.endsWith("_secret_key")
+ || lowerCaseKey.endsWith(".token")
+ || lowerCaseKey.endsWith("_token")
+ || lowerCaseKey.endsWith(".credential")
+ || lowerCaseKey.endsWith("_credential")
+ || lowerCaseKey.endsWith(".keytab")
+ || lowerCaseKey.endsWith("_keytab")
+ || lowerCaseKey.endsWith("keytab_content");
+ }
+
private static TFetchSchemaTableDataResult
viewDependencyMetadataResult(TSchemaTableRequestParams params) {
if (!params.isSetCurrentUserIdent()) {
return errorResult("current user ident is not set.");
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..8bdd9a491dd
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
@@ -0,0 +1,190 @@
+// 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 final String CREATE_USER = "creator";
+ private static final String ALTER_USER = "modifier";
+
+ 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", CREATE_USER);
+
+ Assertions.assertEquals("corp_ldap", meta.getName());
+ Assertions.assertEquals("ldap", meta.getType());
+ Assertions.assertEquals("ldap integration", meta.getComment());
+ Assertions.assertEquals(CREATE_USER, meta.getCreateUser());
+ Assertions.assertEquals(CREATE_USER, meta.getAlterUser());
+ Assertions.assertTrue(meta.getCreateTime() > 0);
+ Assertions.assertEquals(meta.getCreateTime(), meta.getModifyTime());
+ Assertions.assertEquals(meta.getCreateTimeString(),
meta.getModifyTimeString());
+ 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, CREATE_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
Collections.emptyMap(), null, CREATE_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
map("k", "v"), null, CREATE_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> AuthenticationIntegrationMeta.fromCreateSql("i1",
map("type", ""), null, CREATE_USER));
+ }
+
+ @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, CREATE_USER));
+ }
+
+ @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",
+ CREATE_USER);
+
+ AuthenticationIntegrationMeta altered = meta.withAlterProperties(map(
+ "ldap.server", "ldap://new",
+ "ldap.user_filter", "(uid={login})"), ALTER_USER);
+
+ Assertions.assertEquals("ldap", altered.getType());
+ Assertions.assertEquals("old comment", altered.getComment());
+ Assertions.assertEquals(CREATE_USER, altered.getCreateUser());
+ Assertions.assertEquals(ALTER_USER, altered.getAlterUser());
+ Assertions.assertEquals(meta.getCreateTime(), altered.getCreateTime());
+ Assertions.assertTrue(altered.getModifyTime() >= meta.getModifyTime());
+ 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(),
ALTER_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withAlterProperties(map("TYPE", "oidc"),
ALTER_USER));
+ }
+
+ @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",
+ CREATE_USER);
+
+ AuthenticationIntegrationMeta altered =
meta.withUnsetProperties(set("ldap.base_dn"), ALTER_USER);
+ Assertions.assertEquals("ldap", altered.getType());
+ Assertions.assertEquals(CREATE_USER, altered.getCreateUser());
+ Assertions.assertEquals(ALTER_USER, altered.getAlterUser());
+ Assertions.assertEquals(meta.getCreateTime(), altered.getCreateTime());
+ Assertions.assertTrue(altered.getModifyTime() >= meta.getModifyTime());
+
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(),
ALTER_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> meta.withUnsetProperties(set("TYPE"), ALTER_USER));
+ }
+
+ @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",
+ CREATE_USER);
+
+ 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());
+ Assertions.assertEquals(meta.getCreateUser(), read.getCreateUser());
+ Assertions.assertEquals(meta.getCreateTime(), read.getCreateTime());
+ Assertions.assertEquals(meta.getAlterUser(), read.getAlterUser());
+ Assertions.assertEquals(meta.getModifyTime(), read.getModifyTime());
+ }
+}
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..c965cea9f01
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
@@ -0,0 +1,216 @@
+// 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 {
+ private static final String CREATE_USER = "creator";
+ private static final String ALTER_USER = "modifier";
+
+ @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", CREATE_USER);
+ AuthenticationIntegrationMeta created =
mgr.getAuthenticationIntegrations().get("corp_ldap");
+ Assertions.assertNotNull(created);
+ Assertions.assertEquals("ldap", created.getType());
+ Assertions.assertEquals(CREATE_USER, created.getCreateUser());
+ Assertions.assertEquals(CREATE_USER, created.getAlterUser());
+ 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"),
ALTER_USER);
+ Assertions.assertEquals(ALTER_USER,
+
mgr.getAuthenticationIntegrations().get("corp_ldap").getAlterUser());
+ 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"), ALTER_USER);
+ Assertions.assertFalse(mgr.getAuthenticationIntegrations()
+
.get("corp_ldap").getProperties().containsKey("ldap.admin_password"));
+
+ mgr.alterAuthenticationIntegrationComment("corp_ldap", "new comment",
ALTER_USER);
+ 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, CREATE_USER);
+
+ Assertions.assertThrows(DdlException.class,
+ () -> mgr.createAuthenticationIntegration("corp_ldap", false,
map("type", "ldap"), null, CREATE_USER));
+ Assertions.assertDoesNotThrow(
+ () -> mgr.createAuthenticationIntegration("corp_ldap", true,
map("type", "ldap"), null, CREATE_USER));
+
+ 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"),
ALTER_USER));
+ Assertions.assertThrows(DdlException.class,
+ () ->
mgr.alterAuthenticationIntegrationUnsetProperties("not_exist", set("k"),
ALTER_USER));
+ Assertions.assertThrows(DdlException.class,
+ () -> mgr.alterAuthenticationIntegrationComment("not_exist",
"comment", ALTER_USER));
+ }
+
+ @Test
+ public void testReplayAndGetUnmodifiableView() throws Exception {
+ AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+
+ AuthenticationIntegrationMeta meta1 =
AuthenticationIntegrationMeta.fromCreateSql(
+ "corp_ldap", map("type", "ldap", "ldap.server", "ldap://old"),
null, CREATE_USER);
+ AuthenticationIntegrationMeta meta2 =
meta1.withAlterProperties(map("ldap.server", "ldap://new"), ALTER_USER);
+
+ 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",
+ CREATE_USER);
+ 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());
+ Assertions.assertEquals(CREATE_USER, readMeta.getCreateUser());
+ Assertions.assertEquals(CREATE_USER, readMeta.getAlterUser());
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
index 03c6d092eab..9970d50a37a 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
@@ -92,5 +92,15 @@ public class SchemaTableTest {
SchemaTable viewDependency = (SchemaTable)
SchemaTable.TABLE_MAP.get("view_dependency");
Assertions.assertFalse(viewDependency.shouldFetchAllFe());
Assertions.assertFalse(viewDependency.shouldAddAgg());
+
+ SchemaTable authenticationIntegrations =
+ (SchemaTable)
SchemaTable.TABLE_MAP.get("authentication_integrations");
+ Assertions.assertFalse(authenticationIntegrations.shouldFetchAllFe());
+ Assertions.assertFalse(authenticationIntegrations.shouldAddAgg());
+ Assertions.assertEquals(8,
authenticationIntegrations.getFullSchema().size());
+ Assertions.assertEquals("CREATE_USER",
authenticationIntegrations.getFullSchema().get(4).getName());
+ Assertions.assertEquals("CREATE_TIME",
authenticationIntegrations.getFullSchema().get(5).getName());
+ Assertions.assertEquals("ALTER_USER",
authenticationIntegrations.getFullSchema().get(6).getName());
+ Assertions.assertEquals("MODIFY_TIME",
authenticationIntegrations.getFullSchema().get(7).getName());
}
}
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..8d387c5c877
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
@@ -0,0 +1,249 @@
+// 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.analysis.UserIdentity;
+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 {
+ UserIdentity currentUser =
UserIdentity.createAnalyzedUserIdentWithIp("admin", "%");
+ 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;
+
+ connectContext.getQualifiedUser();
+ minTimes = 0;
+ result = currentUser.getQualifiedUser();
+
+ env.getAuthenticationIntegrationMgr();
+ minTimes = 0;
+ result = authenticationIntegrationMgr;
+
+ authenticationIntegrationMgr.createAuthenticationIntegration(
+ anyString, anyBoolean, (Map<String, String>) any,
anyString, 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 {
+ UserIdentity currentUser =
UserIdentity.createAnalyzedUserIdentWithIp("admin", "%");
+ 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;
+
+ connectContext.getQualifiedUser();
+ minTimes = 0;
+ result = currentUser.getQualifiedUser();
+
+ env.getAuthenticationIntegrationMgr();
+ minTimes = 0;
+ result = authenticationIntegrationMgr;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationProperties(
+ anyString, (Map<String, String>) any, anyString);
+ times = 1;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationUnsetProperties(
+ anyString, (Set<String>) any, anyString);
+ times = 1;
+
+
authenticationIntegrationMgr.alterAuthenticationIntegrationComment(anyString,
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/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index f78d1bdf6e7..56d5710f92d 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -23,6 +23,7 @@ import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
import org.apache.doris.common.Config;
import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.util.PrintableMap;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand;
@@ -41,6 +42,7 @@ import org.apache.doris.thrift.TMetadataTableRequestParams;
import org.apache.doris.thrift.TMetadataType;
import org.apache.doris.thrift.TNullableStringLiteral;
import org.apache.doris.thrift.TSchemaTableName;
+import org.apache.doris.thrift.TSchemaTableRequestParams;
import org.apache.doris.thrift.TShowUserRequest;
import org.apache.doris.thrift.TShowUserResult;
import org.apache.doris.thrift.TStatusCode;
@@ -57,6 +59,7 @@ import org.junit.rules.ExpectedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -247,4 +250,57 @@ public class FrontendServiceImplTest {
TShowUserResult result = impl.showUser(request);
System.out.println(result);
}
+
+ @Test
+ public void testFetchAuthenticationIntegrationsSchemaTableData() throws
Exception {
+ String integrationName = "test_authentication_integration";
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+
+ LinkedHashMap<String, String> properties = new LinkedHashMap<>();
+ properties.put("type", "ldap");
+ properties.put("server", "ldap://127.0.0.1:389");
+ properties.put("bind_password", "plain_secret");
+ properties.put("secret.endpoint", "masked_by_prefix");
+ Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+ .createAuthenticationIntegration(
+ integrationName, false, properties, "ldap comment",
connectContext.getQualifiedUser());
+
+ try {
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TFetchSchemaTableDataRequest request = new
TFetchSchemaTableDataRequest();
+
request.setSchemaTableName(TSchemaTableName.AUTHENTICATION_INTEGRATIONS);
+ TSchemaTableRequestParams params = new TSchemaTableRequestParams();
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+ request.setSchemaTableParams(params);
+
+ TFetchSchemaTableDataResult result =
impl.fetchSchemaTableData(request);
+ Assert.assertEquals(TStatusCode.OK,
result.getStatus().getStatusCode());
+
+ List<String> rowValues = result.getDataBatch().stream()
+ .filter(row ->
integrationName.equals(row.getColumnValue().get(0).getStringVal()))
+ .map(row -> row.getColumnValue().stream()
+ .map(cell -> cell.isSetStringVal() ?
cell.getStringVal() : null)
+ .collect(Collectors.toList()))
+ .findFirst()
+ .orElseThrow(() -> new
java.lang.AssertionError("authentication integration row not found"));
+
+ Assert.assertEquals(integrationName, rowValues.get(0));
+ Assert.assertEquals("ldap", rowValues.get(1));
+ Assert.assertTrue(rowValues.get(2).contains("\"server\" =
\"ldap://127.0.0.1:389\""));
+ Assert.assertTrue(rowValues.get(2).contains(
+ "\"bind_password\" = \"" + PrintableMap.PASSWORD_MASK +
"\""));
+ Assert.assertTrue(rowValues.get(2).contains(
+ "\"secret.endpoint\" = \"" + PrintableMap.PASSWORD_MASK +
"\""));
+ Assert.assertFalse(rowValues.get(2).contains("plain_secret"));
+ Assert.assertFalse(rowValues.get(2).contains("masked_by_prefix"));
+ Assert.assertEquals("ldap comment", rowValues.get(3));
+ Assert.assertEquals(connectContext.getQualifiedUser(),
rowValues.get(4));
+ Assert.assertNotNull(rowValues.get(5));
+ Assert.assertFalse(rowValues.get(5).isEmpty());
+ Assert.assertEquals(connectContext.getQualifiedUser(),
rowValues.get(6));
+ Assert.assertEquals(rowValues.get(5), rowValues.get(7));
+ } finally {
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+ }
+ }
}
diff --git a/gensrc/thrift/Descriptors.thrift b/gensrc/thrift/Descriptors.thrift
index fb5fe5109f3..ea915a5c7a8 100644
--- a/gensrc/thrift/Descriptors.thrift
+++ b/gensrc/thrift/Descriptors.thrift
@@ -212,6 +212,7 @@ enum TSchemaTableType {
SCH_BLACKHOLE = 62;
SCH_COLUMN_DATA_SIZES = 63;
SCH_LOAD_JOBS = 64;
+ SCH_AUTHENTICATION_INTEGRATIONS = 65;
}
enum THdfsCompression {
diff --git a/gensrc/thrift/FrontendService.thrift
b/gensrc/thrift/FrontendService.thrift
index f4b155a209a..641624f4d12 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -868,6 +868,7 @@ enum TSchemaTableName {
PARTITIONS = 10,
VIEW_DEPENDENCY = 11,
SQL_BLOCK_RULE_STATUS = 12,
+ AUTHENTICATION_INTEGRATIONS = 13,
}
struct TMetadataTableRequestParams {
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..caec26467d2
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
@@ -0,0 +1,109 @@
+// 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',
+ 'secret.endpoint'='secret_create_value'
+ )
+ 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',
+ 'secret.endpoint'='secret_alter_value'
+ )
+ """
+
+ sql """ALTER AUTHENTICATION INTEGRATION ${integrationName} SET COMMENT
'updated comment'"""
+
+ def result = sql """
+ SELECT
+ NAME,
+ TYPE,
+ PROPERTIES,
+ COMMENT,
+ CREATE_USER,
+ CREATE_TIME,
+ ALTER_USER,
+ MODIFY_TIME
+ FROM information_schema.authentication_integrations
+ WHERE NAME = '${integrationName}'
+ ORDER BY NAME
+ """
+ assertEquals(1, result.size())
+ assertEquals(8, result[0].size())
+ assertEquals(integrationName, result[0][0])
+ assertEquals("ldap", result[0][1])
+ assertTrue(result[0][2].contains("\"ldap.server\" =
\"ldap://127.0.0.1:1389\""))
+ assertTrue(result[0][2].contains("\"ldap.admin_password\" = \"*XXX\""))
+ assertTrue(result[0][2].contains("\"secret.endpoint\" = \"*XXX\""))
+ assertTrue(!result[0][2].contains("abcdef"))
+ assertTrue(!result[0][2].contains("secret_alter_value"))
+ assertEquals("updated comment", result[0][3])
+ assertTrue(result[0][4] != null && result[0][4].length() > 0)
+ assertTrue(result[0][5] != null && result[0][5].length() > 0)
+ assertTrue(result[0][6] != null && result[0][6].length() > 0)
+ assertTrue(result[0][7] != null && result[0][7].length() > 0)
+
+ 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]