From 99d49bdda646e9033ae0e63438324e011fb5a1dd Mon Sep 17 00:00:00 2001 From: guoqiang Date: Sat, 28 Feb 2026 11:43:38 +0800 Subject: [PATCH 1/8] [feat](authentication)Support AuthenticationIntegration DDL --- .../org/apache/doris/nereids/DorisLexer.g4 | 2 + .../org/apache/doris/nereids/DorisParser.g4 | 9 + .../AuthenticationIntegrationMeta.java | 144 ++++++++++++ .../AuthenticationIntegrationMgr.java | 177 ++++++++++++++ .../java/org/apache/doris/catalog/Env.java | 18 ++ .../apache/doris/journal/JournalEntity.java | 13 ++ .../nereids/parser/LogicalPlanBuilder.java | 52 +++++ .../LogicalPlanBuilderForEncryption.java | 25 ++ .../doris/nereids/trees/plans/PlanType.java | 3 + ...AlterAuthenticationIntegrationCommand.java | 115 +++++++++ ...reateAuthenticationIntegrationCommand.java | 87 +++++++ .../DropAuthenticationIntegrationCommand.java | 65 ++++++ ...AuthenticationIntegrationOperationLog.java | 53 +++++ .../org/apache/doris/persist/EditLog.java | 29 +++ .../apache/doris/persist/OperationType.java | 3 + .../doris/persist/meta/MetaPersistMethod.java | 8 + .../persist/meta/PersistMetaModules.java | 3 +- .../AuthenticationIntegrationMetaTest.java | 140 +++++++++++ .../AuthenticationIntegrationMgrTest.java | 188 +++++++++++++++ .../AuthenticationIntegrationParserTest.java | 98 ++++++++ .../doris/nereids/parser/EncryptSQLTest.java | 26 +++ .../AuthenticationIntegrationCommandTest.java | 218 ++++++++++++++++++ ...enticationIntegrationOperationLogTest.java | 47 ++++ ...est_authentication_integration_auth.groovy | 78 +++++++ 24 files changed, 1600 insertions(+), 1 deletion(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java create mode 100644 regression-test/suites/auth_p0/test_authentication_integration_auth.groovy 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 12a73beb2c5559..24423fbab74014 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 @@ -86,6 +86,7 @@ AS: 'AS'; ASC: 'ASC'; AT: 'AT'; AUTHORS: 'AUTHORS'; +AUTHENTICATION: 'AUTHENTICATION'; AUTO: 'AUTO'; AUTO_INCREMENT: 'AUTO_INCREMENT'; ALWAYS: 'ALWAYS'; @@ -294,6 +295,7 @@ IGNORE: 'IGNORE'; IMMEDIATE: 'IMMEDIATE'; IN: 'IN'; INCREMENTAL: 'INCREMENTAL'; +INTEGRATION: 'INTEGRATION'; INDEX: 'INDEX'; INDEXES: 'INDEXES'; INFILE: 'INFILE'; diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 4276eaf55b46d0..49aa492de60ea7 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -208,6 +208,8 @@ supportedCreateStatement LIKE existedTable=multipartIdentifier (WITH ROLLUP (rollupNames=identifierList)?)? #createTableLike | CREATE ROLE (IF NOT EXISTS)? name=identifierOrText (COMMENT STRING_LITERAL)? #createRole + | CREATE AUTHENTICATION INTEGRATION integrationName=identifier + WITH PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN commentSpec? #createAuthenticationIntegration | CREATE WORKLOAD GROUP (IF NOT EXISTS)? name=identifierOrText (FOR computeGroup=identifierOrText)? properties=propertyClause? #createWorkloadGroup | CREATE CATALOG (IF NOT EXISTS)? catalogName=identifier @@ -292,6 +294,10 @@ supportedAlterStatement properties=propertyClause? #alterComputeGroup | ALTER CATALOG name=identifier SET PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN #alterCatalogProperties + | ALTER AUTHENTICATION INTEGRATION integrationName=identifier + SET PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN #alterAuthenticationIntegrationProperties + | ALTER AUTHENTICATION INTEGRATION integrationName=identifier + SET COMMENT comment=STRING_LITERAL #alterAuthenticationIntegrationComment | ALTER WORKLOAD POLICY name=identifierOrText properties=propertyClause? #alterWorkloadPolicy | ALTER SQL_BLOCK_RULE name=identifier properties=propertyClause? #alterSqlBlockRule @@ -335,6 +341,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 @@ -1955,6 +1962,7 @@ nonReserved | ARRAY | AT | AUTHORS + | AUTHENTICATION | AUTO_INCREMENT | BACKENDS | BACKUP @@ -2106,6 +2114,7 @@ nonReserved | IGNORE | IMMEDIATE | INCREMENTAL + | INTEGRATION | INDEXES | INSERT | INVERTED diff --git a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java new file mode 100644 index 00000000000000..29e2cff2324b8c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.authentication; + +import org.apache.doris.common.DdlException; +import org.apache.doris.common.io.Text; +import org.apache.doris.common.io.Writable; +import org.apache.doris.persist.gson.GsonUtils; + +import com.google.gson.annotations.SerializedName; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Persistent metadata for AUTHENTICATION INTEGRATION. + */ +public class AuthenticationIntegrationMeta implements Writable { + public static final String TYPE_PROPERTY = "type"; + + @SerializedName(value = "name") + private String name; + @SerializedName(value = "type") + private String type; + @SerializedName(value = "properties") + private Map properties; + @SerializedName(value = "comment") + private String comment; + + private AuthenticationIntegrationMeta() { + this.name = ""; + this.type = ""; + this.properties = Collections.emptyMap(); + this.comment = null; + } + + public AuthenticationIntegrationMeta(String name, String type, Map properties, String comment) { + this.name = Objects.requireNonNull(name, "name can not be null"); + this.type = Objects.requireNonNull(type, "type can not be null"); + this.properties = Collections.unmodifiableMap( + new LinkedHashMap<>(Objects.requireNonNull(properties, "properties can not be null"))); + this.comment = comment; + } + + /** + * Build metadata from CREATE SQL arguments. + */ + public static AuthenticationIntegrationMeta fromCreateSql( + String integrationName, Map properties, String comment) throws DdlException { + if (properties == null || properties.isEmpty()) { + throw new DdlException("Property 'type' is required in CREATE AUTHENTICATION INTEGRATION"); + } + String type = null; + Map copiedProperties = new LinkedHashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + String key = Objects.requireNonNull(entry.getKey(), "property key can not be null"); + if (TYPE_PROPERTY.equalsIgnoreCase(key)) { + if (type != null) { + throw new DdlException("Property 'type' is duplicated in CREATE AUTHENTICATION INTEGRATION"); + } + type = entry.getValue(); + continue; + } + copiedProperties.put(key, entry.getValue()); + } + if (type == null || type.isEmpty()) { + throw new DdlException("Property 'type' is required in CREATE AUTHENTICATION INTEGRATION"); + } + return new AuthenticationIntegrationMeta(integrationName, type, copiedProperties, comment); + } + + /** + * Build a new metadata object after ALTER ... SET PROPERTIES. + */ + public AuthenticationIntegrationMeta withAlterProperties(Map propertiesDelta) throws DdlException { + if (propertiesDelta == null || propertiesDelta.isEmpty()) { + throw new DdlException("ALTER AUTHENTICATION INTEGRATION should contain at least one property"); + } + for (String key : propertiesDelta.keySet()) { + if (TYPE_PROPERTY.equalsIgnoreCase(key)) { + throw new DdlException("ALTER AUTHENTICATION INTEGRATION does not allow modifying property 'type'"); + } + } + Map mergedProperties = new LinkedHashMap<>(properties); + mergedProperties.putAll(propertiesDelta); + return new AuthenticationIntegrationMeta(name, type, mergedProperties, comment); + } + + public AuthenticationIntegrationMeta withComment(String newComment) { + return new AuthenticationIntegrationMeta(name, type, properties, newComment); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public Map getProperties() { + return properties; + } + + public String getComment() { + return comment; + } + + public Map toSqlPropertiesView() { + Map 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 00000000000000..0db39dbb4c72b1 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java @@ -0,0 +1,177 @@ +// 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.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.concurrent.locks.ReentrantReadWriteLock; + +/** + * Manager for AUTHENTICATION INTEGRATION metadata. + */ +public class AuthenticationIntegrationMgr implements Writable { + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); + + @SerializedName(value = "nameToIntegration") + private Map 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, Map properties, String comment) throws DdlException { + AuthenticationIntegrationMeta meta = + AuthenticationIntegrationMeta.fromCreateSql(integrationName, properties, comment); + writeLock(); + try { + if (nameToIntegration.containsKey(integrationName)) { + throw new DdlException("Authentication integration " + integrationName + " already exists"); + } + nameToIntegration.put(integrationName, meta); + Env.getCurrentEnv().getEditLog().logCreateAuthenticationIntegration(meta); + } finally { + writeUnlock(); + } + } + + public void alterAuthenticationIntegrationProperties( + String integrationName, Map properties) throws DdlException { + writeLock(); + try { + AuthenticationIntegrationMeta current = getOrThrow(integrationName); + AuthenticationIntegrationMeta updated = current.withAlterProperties(properties); + nameToIntegration.put(integrationName, updated); + Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); + } finally { + writeUnlock(); + } + } + + public void alterAuthenticationIntegrationComment(String integrationName, String comment) throws DdlException { + writeLock(); + try { + AuthenticationIntegrationMeta current = getOrThrow(integrationName); + AuthenticationIntegrationMeta updated = current.withComment(comment); + nameToIntegration.put(integrationName, updated); + 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); + 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 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 e68b71e9f9d101..49ed49976a9ed2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @@ -27,6 +27,7 @@ import org.apache.doris.analysis.DistributionDesc; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.SlotRef; +import org.apache.doris.authentication.AuthenticationIntegrationMgr; import org.apache.doris.backup.BackupHandler; import org.apache.doris.backup.RestoreJob; import org.apache.doris.binlog.BinlogGcer; @@ -375,6 +376,7 @@ public class Env { private RoutineLoadManager routineLoadManager; private GroupCommitManager groupCommitManager; private SqlBlockRuleMgr sqlBlockRuleMgr; + private AuthenticationIntegrationMgr authenticationIntegrationMgr; private ExportMgr exportMgr; private Alter alter; private ConsistencyChecker consistencyChecker; @@ -704,6 +706,7 @@ public Env(boolean isCheckpointCatalog) { this.routineLoadManager = EnvFactory.getInstance().createRoutineLoadManager(); this.groupCommitManager = new GroupCommitManager(); this.sqlBlockRuleMgr = new SqlBlockRuleMgr(); + this.authenticationIntegrationMgr = new AuthenticationIntegrationMgr(); this.exportMgr = new ExportMgr(); this.alter = new Alter(); this.consistencyChecker = new ConsistencyChecker(); @@ -2488,6 +2491,12 @@ public long loadSqlBlockRule(DataInputStream in, long checksum) throws IOExcepti return checksum; } + public long loadAuthenticationIntegrations(DataInputStream in, long checksum) throws IOException { + authenticationIntegrationMgr = AuthenticationIntegrationMgr.read(in); + LOG.info("finished replay authentication integrations from image"); + return checksum; + } + /** * Load policy through file. **/ @@ -2800,6 +2809,11 @@ public long saveSqlBlockRule(CountingDataOutputStream out, long checksum) throws return checksum; } + public long saveAuthenticationIntegrations(CountingDataOutputStream out, long checksum) throws IOException { + Env.getCurrentEnv().getAuthenticationIntegrationMgr().write(out); + return checksum; + } + public long savePolicy(CountingDataOutputStream out, long checksum) throws IOException { Env.getCurrentEnv().getPolicyMgr().write(out); return checksum; @@ -5152,6 +5166,10 @@ public SqlBlockRuleMgr getSqlBlockRuleMgr() { return sqlBlockRuleMgr; } + public AuthenticationIntegrationMgr getAuthenticationIntegrationMgr() { + return authenticationIntegrationMgr; + } + public RoutineLoadTaskScheduler getRoutineLoadTaskScheduler() { return routineLoadTaskScheduler; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java index 76b4578f892c2b..b5e2b930de4953 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.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; @@ -88,6 +89,7 @@ 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; @@ -708,6 +710,17 @@ public void readFields(DataInput in) throws IOException { 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 b104f33207607b..db981250052281 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 @@ -635,6 +635,7 @@ 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; @@ -676,6 +677,7 @@ 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; @@ -707,6 +709,7 @@ 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; @@ -5038,6 +5041,15 @@ private String parsePropertyValue(PropertyValueContext item) { return item.getText(); } + private boolean containsPropertyKeyIgnoreCase(Map properties, String expectedKey) { + for (String key : properties.keySet()) { + if (key.equalsIgnoreCase(expectedKey)) { + return true; + } + } + return false; + } + private ExplainLevel parseExplainPlanType(PlanTypeContext planTypeContext) { if (planTypeContext == null || planTypeContext.ALL() != null) { return ExplainLevel.ALL_PLAN; @@ -7033,6 +7045,18 @@ public LogicalPlan visitCreateCatalog(CreateCatalogContext ctx) { return new CreateCatalogCommand(catalogName, ifNotExists, resourceName, comment, properties); } + @Override + public LogicalPlan visitCreateAuthenticationIntegration( + DorisParser.CreateAuthenticationIntegrationContext ctx) { + String integrationName = stripQuotes(ctx.integrationName.getText()); + Map properties = Maps.newHashMap(visitPropertyItemList(ctx.propertyItemList())); + if (!containsPropertyKeyIgnoreCase(properties, "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, properties, comment); + } + @Override public LogicalPlan visitShowStages(ShowStagesContext ctx) { return new ShowStagesCommand(); @@ -7069,6 +7093,26 @@ public LogicalPlan visitAlterCatalogProperties(AlterCatalogPropertiesContext ctx return new AlterCatalogPropertiesCommand(catalogName, properties); } + @Override + public LogicalPlan visitAlterAuthenticationIntegrationProperties( + DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) { + String integrationName = stripQuotes(ctx.integrationName.getText()); + Map properties = Maps.newHashMap(visitPropertyItemList(ctx.propertyItemList())); + if (containsPropertyKeyIgnoreCase(properties, "type")) { + throw new ParseException( + "ALTER AUTHENTICATION INTEGRATION does not allow modifying property 'type'", ctx); + } + return AlterAuthenticationIntegrationCommand.forSetProperties(integrationName, properties); + } + + @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 dbTblNameParts = visitMultipartIdentifier(ctx.name); @@ -7187,6 +7231,14 @@ public LogicalPlan visitDropCatalog(DropCatalogContext ctx) { 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 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 3c9eea1d386e89..49aeb374dac1a3 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 LogicalPlan visitCreateStorageVault(DorisParser.CreateStorageVaultContext return super.visitCreateStorageVault(ctx); } + // create authentication integration clause + @Override + public LogicalPlan visitCreateAuthenticationIntegration(DorisParser.CreateAuthenticationIntegrationContext ctx) { + if (ctx.propertyItemList() != null) { + DorisParser.PropertyItemListContext propertyItemListContext = ctx.propertyItemList(); + encryptProperty(visitPropertyItemList(propertyItemListContext), + propertyItemListContext.start.getStartIndex(), + propertyItemListContext.stop.getStopIndex()); + } + return super.visitCreateAuthenticationIntegration(ctx); + } + // alter storage vault clause @Override public LogicalPlan visitAlterStorageVault(DorisParser.AlterStorageVaultContext ctx) { @@ -144,6 +156,19 @@ public LogicalPlan visitAlterStorageVault(DorisParser.AlterStorageVaultContext c return super.visitAlterStorageVault(ctx); } + // alter authentication integration properties clause + @Override + public LogicalPlan visitAlterAuthenticationIntegrationProperties( + DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) { + if (ctx.propertyItemList() != null) { + DorisParser.PropertyItemListContext propertyItemListContext = ctx.propertyItemList(); + encryptProperty(visitPropertyItemList(propertyItemListContext), + propertyItemListContext.start.getStartIndex(), + propertyItemListContext.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 5b89fa7b89bd59..e413a53ead5a6e 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 @@ -155,6 +155,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, @@ -183,10 +184,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 00000000000000..67fefc2e4b98c7 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java @@ -0,0 +1,115 @@ +// 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.Map; +import java.util.Objects; + +/** + * ALTER AUTHENTICATION INTEGRATION command entry. + */ +public class AlterAuthenticationIntegrationCommand extends AlterCommand implements NeedAuditEncryption { + /** alter action. */ + public enum AlterType { + SET_PROPERTIES, + SET_COMMENT + } + + private final String integrationName; + private final AlterType alterType; + private final Map properties; + private final String comment; + + private AlterAuthenticationIntegrationCommand(String integrationName, AlterType alterType, + Map properties, 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.comment = comment; + } + + public static AlterAuthenticationIntegrationCommand forSetProperties(String integrationName, + Map properties) { + return new AlterAuthenticationIntegrationCommand( + integrationName, AlterType.SET_PROPERTIES, properties, null); + } + + public static AlterAuthenticationIntegrationCommand forSetComment(String integrationName, String comment) { + return new AlterAuthenticationIntegrationCommand( + integrationName, AlterType.SET_COMMENT, Collections.emptyMap(), comment); + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitCommand(this, context); + } + + @Override + public void doRun(ConnectContext ctx, StmtExecutor executor) throws Exception { + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + switch (alterType) { + case SET_PROPERTIES: + Env.getCurrentEnv().getAuthenticationIntegrationMgr() + .alterAuthenticationIntegrationProperties(integrationName, properties); + return; + case SET_COMMENT: + Env.getCurrentEnv().getAuthenticationIntegrationMgr() + .alterAuthenticationIntegrationComment(integrationName, comment); + return; + default: + throw new AnalysisException("Unsupported alter type for AUTHENTICATION INTEGRATION: " + alterType); + } + } + + @Override + public boolean needAuditEncryption() { + return true; + } + + public String getIntegrationName() { + return integrationName; + } + + public AlterType getAlterType() { + return alterType; + } + + public Map getProperties() { + return properties; + } + + 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 00000000000000..b837ed91a47dc6 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java @@ -0,0 +1,87 @@ +// 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 Map properties; + private final String comment; + + public CreateAuthenticationIntegrationCommand(String integrationName, + Map properties, String comment) { + super(PlanType.CREATE_AUTHENTICATION_INTEGRATION_COMMAND); + this.integrationName = Objects.requireNonNull(integrationName, "integrationName can not be null"); + this.properties = Collections.unmodifiableMap( + new LinkedHashMap<>(Objects.requireNonNull(properties, "properties can not be null"))); + this.comment = comment; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitCommand(this, context); + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + Env.getCurrentEnv().getAuthenticationIntegrationMgr() + .createAuthenticationIntegration(integrationName, properties, comment); + } + + @Override + public StmtType stmtType() { + return StmtType.CREATE; + } + + @Override + public boolean needAuditEncryption() { + return true; + } + + public String getIntegrationName() { + return integrationName; + } + + public Map 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 00000000000000..112148423bc6ea --- /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 accept(PlanVisitor visitor, C context) { + return visitor.visitCommand(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/persist/DropAuthenticationIntegrationOperationLog.java b/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java new file mode 100644 index 00000000000000..c9a05d13ef2bad --- /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 = "integrationName") + 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 d506b474ed70b3..69d10cfd3a7d85 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.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; @@ -1082,6 +1083,22 @@ public static void loadJournal(Env env, Long logId, JournalEntity journal) { 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); @@ -2312,6 +2329,18 @@ public void logDropSqlBlockRule(List ruleNames) { 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 1174d9c3874817..82eea42ae70560 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java @@ -418,6 +418,9 @@ public class OperationType { public static final short OP_DROP_INDEX_POLICY = 491; public static final short OP_OPERATE_KEY = 492; + public static final short OP_CREATE_AUTHENTICATION_INTEGRATION = 493; + public static final short OP_ALTER_AUTHENTICATION_INTEGRATION = 494; + public static final short OP_DROP_AUTHENTICATION_INTEGRATION = 495; // For cloud. public static final short OP_UPDATE_CLOUD_REPLICA = 1000; diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java index 0114bde9eee3e0..e6699ef1a10545 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,14 @@ public static MetaPersistMethod create(String name) throws NoSuchMethodException metaPersistMethod.writeMethod = Env.class.getDeclaredMethod("saveSqlBlockRule", CountingDataOutputStream.class, long.class); break; + 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 665f3cb09ed035..6d1f4afa36a7cb 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,7 +40,8 @@ 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", "authenticationIntegrations", "policy", + "globalFunction", "workloadGroups", "binlogs", "resourceGroups", "AnalysisMgrV2", "AsyncJobManager", "workloadSchedPolicy", "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", "KeyManagerStore"); 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 00000000000000..196e0541b1753b --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java @@ -0,0 +1,140 @@ +// 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.Map; + +public class AuthenticationIntegrationMetaTest { + + private static Map map(String... kvs) { + Map result = new LinkedHashMap<>(); + for (int i = 0; i < kvs.length; i += 2) { + result.put(kvs[i], kvs[i + 1]); + } + return result; + } + + @Test + public void testFromCreateSqlSuccessAndTypeFiltered() throws Exception { + Map properties = new LinkedHashMap<>(); + properties.put("TYPE", "ldap"); + properties.put("ldap.server", "ldap://127.0.0.1:389"); + properties.put("ldap.admin_dn", "cn=admin,dc=example,dc=com"); + + AuthenticationIntegrationMeta meta = + AuthenticationIntegrationMeta.fromCreateSql("corp_ldap", properties, "ldap integration"); + + Assertions.assertEquals("corp_ldap", meta.getName()); + Assertions.assertEquals("ldap", meta.getType()); + Assertions.assertEquals("ldap integration", meta.getComment()); + Assertions.assertEquals(2, meta.getProperties().size()); + Assertions.assertEquals("ldap://127.0.0.1:389", meta.getProperties().get("ldap.server")); + Assertions.assertFalse(meta.getProperties().containsKey("type")); + Assertions.assertFalse(meta.getProperties().containsKey("TYPE")); + + Assertions.assertThrows(UnsupportedOperationException.class, + () -> meta.getProperties().put("x", "y")); + + Map sqlProperties = meta.toSqlPropertiesView(); + Assertions.assertEquals("ldap", sqlProperties.get("type")); + Assertions.assertEquals("cn=admin,dc=example,dc=com", sqlProperties.get("ldap.admin_dn")); + } + + @Test + public void testFromCreateSqlRequireType() { + Assertions.assertThrows(DdlException.class, + () -> AuthenticationIntegrationMeta.fromCreateSql("i1", null, null)); + Assertions.assertThrows(DdlException.class, + () -> AuthenticationIntegrationMeta.fromCreateSql("i1", Collections.emptyMap(), null)); + Assertions.assertThrows(DdlException.class, + () -> AuthenticationIntegrationMeta.fromCreateSql("i1", map("k", "v"), null)); + Assertions.assertThrows(DdlException.class, + () -> AuthenticationIntegrationMeta.fromCreateSql("i1", map("type", ""), null)); + } + + @Test + public void testFromCreateSqlRejectDuplicatedTypeIgnoreCase() { + Map properties = new LinkedHashMap<>(); + properties.put("type", "ldap"); + properties.put("TYPE", "oidc"); + + Assertions.assertThrows(DdlException.class, + () -> AuthenticationIntegrationMeta.fromCreateSql("i1", properties, null)); + } + + @Test + public void testWithAlterProperties() throws Exception { + AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql( + "corp_ldap", + map("type", "ldap", + "ldap.server", "ldap://old", + "ldap.base_dn", "dc=example,dc=com"), + "old comment"); + + AuthenticationIntegrationMeta altered = meta.withAlterProperties(map( + "ldap.server", "ldap://new", + "ldap.user_filter", "(uid={login})")); + + Assertions.assertEquals("ldap", altered.getType()); + Assertions.assertEquals("old comment", altered.getComment()); + Assertions.assertEquals("ldap://new", altered.getProperties().get("ldap.server")); + Assertions.assertEquals("(uid={login})", altered.getProperties().get("ldap.user_filter")); + + Assertions.assertThrows(DdlException.class, + () -> meta.withAlterProperties(Collections.emptyMap())); + Assertions.assertThrows(DdlException.class, + () -> meta.withAlterProperties(map("TYPE", "oidc"))); + } + + @Test + public void testWriteReadRoundTrip() throws IOException, DdlException { + AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql( + "corp_ldap", + map("type", "ldap", + "ldap.server", "ldap://127.0.0.1:389", + "ldap.admin_password", "123456"), + "comment"); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(bos)) { + meta.write(dos); + } + + AuthenticationIntegrationMeta read; + try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + read = AuthenticationIntegrationMeta.read(dis); + } + + Assertions.assertEquals(meta.getName(), read.getName()); + Assertions.assertEquals(meta.getType(), read.getType()); + Assertions.assertEquals(meta.getComment(), read.getComment()); + Assertions.assertEquals(meta.getProperties(), read.getProperties()); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java new file mode 100644 index 00000000000000..479532d321acb5 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java @@ -0,0 +1,188 @@ +// 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.LinkedHashMap; +import java.util.Map; + +public class AuthenticationIntegrationMgrTest { + + @Mocked + private Env env; + + @Mocked + private EditLog editLog; + + private static Map map(String... kvs) { + Map result = new LinkedHashMap<>(); + for (int i = 0; i < kvs.length; i += 2) { + result.put(kvs[i], kvs[i + 1]); + } + 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 createProperties = new LinkedHashMap<>(); + createProperties.put("type", "ldap"); + createProperties.put("ldap.server", "ldap://127.0.0.1:389"); + + mgr.createAuthenticationIntegration("corp_ldap", createProperties, "comment"); + AuthenticationIntegrationMeta created = mgr.getAuthenticationIntegrations().get("corp_ldap"); + Assertions.assertNotNull(created); + Assertions.assertEquals("ldap", created.getType()); + Assertions.assertEquals("ldap://127.0.0.1:389", created.getProperties().get("ldap.server")); + + mgr.alterAuthenticationIntegrationProperties("corp_ldap", map("ldap.server", "ldap://127.0.0.1:1389")); + Assertions.assertEquals("ldap://127.0.0.1:1389", + mgr.getAuthenticationIntegrations().get("corp_ldap").getProperties().get("ldap.server")); + + mgr.alterAuthenticationIntegrationComment("corp_ldap", "new comment"); + Assertions.assertEquals("new comment", mgr.getAuthenticationIntegrations().get("corp_ldap").getComment()); + + mgr.dropAuthenticationIntegration("corp_ldap", false); + Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty()); + } + + @Test + public void testCreateDuplicateAndDropIfExists() throws Exception { + new Expectations() { + { + Env.getCurrentEnv(); + minTimes = 0; + result = env; + + env.getEditLog(); + minTimes = 0; + result = editLog; + + editLog.logCreateAuthenticationIntegration((AuthenticationIntegrationMeta) any); + minTimes = 0; + + editLog.logDropAuthenticationIntegration((DropAuthenticationIntegrationOperationLog) any); + minTimes = 0; + } + }; + + AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); + mgr.createAuthenticationIntegration("corp_ldap", map( + "type", "ldap", + "ldap.server", "ldap://127.0.0.1:389"), null); + + Assertions.assertThrows(DdlException.class, + () -> mgr.createAuthenticationIntegration("corp_ldap", map("type", "ldap"), null)); + + Assertions.assertDoesNotThrow(() -> mgr.dropAuthenticationIntegration("not_exist", true)); + Assertions.assertThrows(DdlException.class, + () -> mgr.dropAuthenticationIntegration("not_exist", false)); + } + + @Test + public void testAlterNotExistThrows() { + AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); + Assertions.assertThrows(DdlException.class, + () -> mgr.alterAuthenticationIntegrationProperties("not_exist", map("k", "v"))); + Assertions.assertThrows(DdlException.class, + () -> mgr.alterAuthenticationIntegrationComment("not_exist", "comment")); + } + + @Test + public void testReplayAndGetUnmodifiableView() throws Exception { + AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); + + AuthenticationIntegrationMeta meta1 = AuthenticationIntegrationMeta.fromCreateSql( + "corp_ldap", map("type", "ldap", "ldap.server", "ldap://old"), null); + AuthenticationIntegrationMeta meta2 = meta1.withAlterProperties(map("ldap.server", "ldap://new")); + + mgr.replayCreateAuthenticationIntegration(meta1); + mgr.replayAlterAuthenticationIntegration(meta2); + + Map copy = mgr.getAuthenticationIntegrations(); + Assertions.assertEquals(1, copy.size()); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> copy.put("x", meta1)); + + mgr.replayDropAuthenticationIntegration(new DropAuthenticationIntegrationOperationLog("corp_ldap")); + Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty()); + } + + @Test + public void testWriteReadRoundTrip() throws IOException, DdlException { + AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); + AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql( + "corp_ldap", map( + "type", "ldap", + "ldap.server", "ldap://127.0.0.1:389"), + "comment"); + mgr.replayCreateAuthenticationIntegration(meta); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(bos)) { + mgr.write(dos); + } + + AuthenticationIntegrationMgr read; + try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + read = AuthenticationIntegrationMgr.read(dis); + } + + Assertions.assertEquals(1, read.getAuthenticationIntegrations().size()); + AuthenticationIntegrationMeta readMeta = read.getAuthenticationIntegrations().get("corp_ldap"); + Assertions.assertNotNull(readMeta); + Assertions.assertEquals("ldap", readMeta.getType()); + Assertions.assertEquals("ldap://127.0.0.1:389", readMeta.getProperties().get("ldap.server")); + Assertions.assertEquals("comment", readMeta.getComment()); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java new file mode 100644 index 00000000000000..cb4344ffda036f --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java @@ -0,0 +1,98 @@ +// 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 corp_ldap " + + "WITH 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.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 " + + "WITH 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 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')")); + } + + @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/parser/EncryptSQLTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java index c2a39e366e3738..2870793c88f0fd 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java @@ -393,6 +393,32 @@ public boolean isForwardToMaster() { + "TO DATABASE targetDB"; parseAndCheck(sql, res); + sql = "CREATE AUTHENTICATION INTEGRATION auth_ldap " + + "WITH PROPERTIES (" + + " \"type\" = \"ldap\"," + + " \"ldap.server\" = \"ldap://127.0.0.1:389\"," + + " \"ldap.admin_password\" = \"123456\"" + + ") COMMENT \"for test\""; + res = "CREATE AUTHENTICATION INTEGRATION auth_ldap " + + "WITH PROPERTIES (" + + " \"type\" = \"ldap\"," + + " \"ldap.server\" = \"ldap://127.0.0.1:389\"," + + " \"ldap.admin_password\" = \"*XXX\"" + + ") COMMENT \"for test\""; + parseAndCheck(sql, res); + + sql = "ALTER AUTHENTICATION INTEGRATION auth_ldap " + + "SET PROPERTIES (" + + " \"ldap.admin_password\" = \"abcdef\"," + + " \"ldap.server\" = \"ldap://127.0.0.1:1389\"" + + ")"; + res = "ALTER AUTHENTICATION INTEGRATION auth_ldap " + + "SET PROPERTIES (" + + " \"ldap.admin_password\" = \"*XXX\"," + + " \"ldap.server\" = \"ldap://127.0.0.1:1389\"" + + ")"; + parseAndCheck(sql, res); + sql = "selected * from tbl"; res = "Syntax Error"; parseAndCheck(sql, res); 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 00000000000000..486fe31679fc35 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java @@ -0,0 +1,218 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.nereids.trees.plans.commands; + +import org.apache.doris.analysis.StmtType; +import org.apache.doris.authentication.AuthenticationIntegrationMgr; +import org.apache.doris.catalog.Env; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.mysql.privilege.AccessControllerManager; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.qe.ConnectContext; + +import mockit.Expectations; +import mockit.Mocked; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class AuthenticationIntegrationCommandTest { + + @Mocked + private Env env; + + @Mocked + private AccessControllerManager accessManager; + + @Mocked + private AuthenticationIntegrationMgr authenticationIntegrationMgr; + + @Mocked + private ConnectContext connectContext; + + private static Map map(String... kvs) { + Map result = new LinkedHashMap<>(); + for (int i = 0; i < kvs.length; i += 2) { + result.put(kvs[i], kvs[i + 1]); + } + return result; + } + + @Test + public void testCreateCommandRunAndDenied() throws Exception { + CreateAuthenticationIntegrationCommand createCommand = + new CreateAuthenticationIntegrationCommand("corp_ldap", + map("type", "ldap", "ldap.server", "ldap://127.0.0.1:389"), "comment"); + + new Expectations() { + { + Env.getCurrentEnv(); + minTimes = 0; + result = env; + + env.getAccessManager(); + minTimes = 0; + result = accessManager; + + accessManager.checkGlobalPriv((ConnectContext) any, PrivPredicate.ADMIN); + result = true; + + env.getAuthenticationIntegrationMgr(); + minTimes = 0; + result = authenticationIntegrationMgr; + + authenticationIntegrationMgr.createAuthenticationIntegration(anyString, (Map) any, anyString); + times = 1; + } + }; + + Assertions.assertDoesNotThrow(() -> createCommand.run(connectContext, null)); + Assertions.assertEquals(StmtType.CREATE, createCommand.stmtType()); + Assertions.assertTrue(createCommand.needAuditEncryption()); + + new Expectations() { + { + Env.getCurrentEnv(); + minTimes = 0; + result = env; + + env.getAccessManager(); + minTimes = 0; + result = accessManager; + + accessManager.checkGlobalPriv((ConnectContext) any, PrivPredicate.ADMIN); + result = false; + } + }; + + Assertions.assertThrows(AnalysisException.class, () -> createCommand.run(connectContext, null)); + } + + @Test + public void testAlterCommandRun() throws Exception { + AlterAuthenticationIntegrationCommand setPropertiesCommand = + AlterAuthenticationIntegrationCommand.forSetProperties( + "corp_ldap", map("ldap.server", "ldap://127.0.0.1:1389")); + AlterAuthenticationIntegrationCommand setCommentCommand = + AlterAuthenticationIntegrationCommand.forSetComment("corp_ldap", "new comment"); + + new Expectations() { + { + Env.getCurrentEnv(); + minTimes = 0; + result = env; + + env.getAccessManager(); + minTimes = 0; + result = accessManager; + + accessManager.checkGlobalPriv((ConnectContext) any, PrivPredicate.ADMIN); + minTimes = 0; + result = true; + + env.getAuthenticationIntegrationMgr(); + minTimes = 0; + result = authenticationIntegrationMgr; + + authenticationIntegrationMgr.alterAuthenticationIntegrationProperties(anyString, (Map) any); + times = 1; + + authenticationIntegrationMgr.alterAuthenticationIntegrationComment(anyString, anyString); + times = 1; + } + }; + + Assertions.assertDoesNotThrow(() -> setPropertiesCommand.doRun(connectContext, null)); + Assertions.assertDoesNotThrow(() -> setCommentCommand.doRun(connectContext, null)); + Assertions.assertTrue(setPropertiesCommand.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 00000000000000..042a7fc40d27d3 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.persist; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; + +public class DropAuthenticationIntegrationOperationLogTest { + + @Test + public void testWriteReadRoundTrip() throws Exception { + DropAuthenticationIntegrationOperationLog log = + new DropAuthenticationIntegrationOperationLog("corp_ldap"); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(bos)) { + log.write(dos); + } + + DropAuthenticationIntegrationOperationLog read; + try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + read = DropAuthenticationIntegrationOperationLog.read(dis); + } + + Assertions.assertEquals("corp_ldap", read.getIntegrationName()); + } +} diff --git a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy new file mode 100644 index 00000000000000..3405d6a818fa4c --- /dev/null +++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("test_authentication_integration_auth", "p0,auth") { + String suiteName = "test_authentication_integration_auth" + String integrationName = "${suiteName}_ldap" + + try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}") + + try { + test { + sql """ + CREATE AUTHENTICATION INTEGRATION ${integrationName} + WITH PROPERTIES ('ldap.server'='ldap://127.0.0.1:389') + """ + exception "Property 'type' is required" + } + + sql """ + CREATE AUTHENTICATION INTEGRATION ${integrationName} + WITH PROPERTIES ( + 'type'='ldap', + 'ldap.server'='ldap://127.0.0.1:389', + 'ldap.admin_password'='123456' + ) + COMMENT 'for regression test' + """ + + test { + sql """ + CREATE AUTHENTICATION INTEGRATION ${integrationName} + WITH PROPERTIES ('type'='ldap', 'ldap.server'='ldap://127.0.0.1:1389') + """ + exception "already exists" + } + + test { + sql """ + ALTER AUTHENTICATION INTEGRATION ${integrationName} + SET PROPERTIES ('type'='oidc') + """ + exception "does not allow modifying property 'type'" + } + + sql """ + ALTER AUTHENTICATION INTEGRATION ${integrationName} + SET PROPERTIES ( + 'ldap.server'='ldap://127.0.0.1:1389', + 'ldap.admin_password'='abcdef' + ) + """ + + sql """ALTER AUTHENTICATION INTEGRATION ${integrationName} SET COMMENT 'updated comment'""" + + test { + sql """DROP AUTHENTICATION INTEGRATION ${integrationName}_not_exist""" + exception "does not exist" + } + + sql """DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}_not_exist""" + } finally { + try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}") + } +} From f422c190cedf55d028669237a2d5925d8d0c98aa Mon Sep 17 00:00:00 2001 From: guoqiang Date: Sat, 28 Feb 2026 18:19:44 +0800 Subject: [PATCH 2/8] fix --- .../doris/nereids/parser/EncryptSQLTest.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java index 2870793c88f0fd..c2a39e366e3738 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/EncryptSQLTest.java @@ -393,32 +393,6 @@ public boolean isForwardToMaster() { + "TO DATABASE targetDB"; parseAndCheck(sql, res); - sql = "CREATE AUTHENTICATION INTEGRATION auth_ldap " - + "WITH PROPERTIES (" - + " \"type\" = \"ldap\"," - + " \"ldap.server\" = \"ldap://127.0.0.1:389\"," - + " \"ldap.admin_password\" = \"123456\"" - + ") COMMENT \"for test\""; - res = "CREATE AUTHENTICATION INTEGRATION auth_ldap " - + "WITH PROPERTIES (" - + " \"type\" = \"ldap\"," - + " \"ldap.server\" = \"ldap://127.0.0.1:389\"," - + " \"ldap.admin_password\" = \"*XXX\"" - + ") COMMENT \"for test\""; - parseAndCheck(sql, res); - - sql = "ALTER AUTHENTICATION INTEGRATION auth_ldap " - + "SET PROPERTIES (" - + " \"ldap.admin_password\" = \"abcdef\"," - + " \"ldap.server\" = \"ldap://127.0.0.1:1389\"" - + ")"; - res = "ALTER AUTHENTICATION INTEGRATION auth_ldap " - + "SET PROPERTIES (" - + " \"ldap.admin_password\" = \"*XXX\"," - + " \"ldap.server\" = \"ldap://127.0.0.1:1389\"" - + ")"; - parseAndCheck(sql, res); - sql = "selected * from tbl"; res = "Syntax Error"; parseAndCheck(sql, res); From ee39611eac4a6bb896a99cebc4aa72b4ed491334 Mon Sep 17 00:00:00 2001 From: guoqiang Date: Wed, 4 Mar 2026 17:48:37 +0800 Subject: [PATCH 3/8] fix --- .../org/apache/doris/nereids/DorisParser.g4 | 16 +++++-- .../AuthenticationIntegrationMeta.java | 20 ++++++++- .../AuthenticationIntegrationMgr.java | 20 ++++++++- .../nereids/parser/LogicalPlanBuilder.java | 44 ++++++++++++++++--- ...AlterAuthenticationIntegrationCommand.java | 28 ++++++++++-- ...reateAuthenticationIntegrationCommand.java | 13 ++++-- .../DropAuthenticationIntegrationCommand.java | 2 +- .../trees/plans/visitor/CommandVisitor.java | 18 ++++++++ ...AuthenticationIntegrationOperationLog.java | 2 +- .../persist/meta/PersistMetaModules.java | 5 ++- .../AuthenticationIntegrationMetaTest.java | 28 ++++++++++++ .../AuthenticationIntegrationMgrTest.java | 24 ++++++++-- .../AuthenticationIntegrationParserTest.java | 19 ++++++-- .../AuthenticationIntegrationCommandTest.java | 26 +++++++++-- 14 files changed, 233 insertions(+), 32 deletions(-) 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 49aa492de60ea7..3ea0f718623171 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -208,8 +208,8 @@ supportedCreateStatement LIKE existedTable=multipartIdentifier (WITH ROLLUP (rollupNames=identifierList)?)? #createTableLike | CREATE ROLE (IF NOT EXISTS)? name=identifierOrText (COMMENT STRING_LITERAL)? #createRole - | CREATE AUTHENTICATION INTEGRATION integrationName=identifier - WITH PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN commentSpec? #createAuthenticationIntegration + | 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,7 +295,9 @@ supportedAlterStatement | ALTER CATALOG name=identifier SET PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN #alterCatalogProperties | ALTER AUTHENTICATION INTEGRATION integrationName=identifier - SET PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN #alterAuthenticationIntegrationProperties + 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 @@ -1461,10 +1463,18 @@ propertyClause : PROPERTIES LEFT_PAREN fileProperties=propertyItemList RIGHT_PAREN ; +propertyKeyClause + : PROPERTIES LEFT_PAREN propertyKeyList RIGHT_PAREN + ; + propertyItemList : properties+=propertyItem (COMMA properties+=propertyItem)* ; +propertyKeyList + : keys+=propertyKey (COMMA keys+=propertyKey)* + ; + propertyItem : key=propertyKey EQ value=propertyValue ; 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 index 29e2cff2324b8c..687d5baff43dda 100644 --- 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 @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Persistent metadata for AUTHENTICATION INTEGRATION. @@ -42,7 +43,7 @@ public class AuthenticationIntegrationMeta implements Writable { private String name; @SerializedName(value = "type") private String type; - @SerializedName(value = "properties") + @SerializedName(value = "p") private Map properties; @SerializedName(value = "comment") private String comment; @@ -106,6 +107,23 @@ public AuthenticationIntegrationMeta withAlterProperties(Map pro return new AuthenticationIntegrationMeta(name, type, mergedProperties, comment); } + /** + * Build a new metadata object after ALTER ... UNSET PROPERTIES. + */ + public AuthenticationIntegrationMeta withUnsetProperties(Set propertiesToUnset) throws DdlException { + if (propertiesToUnset == null || propertiesToUnset.isEmpty()) { + throw new DdlException("ALTER AUTHENTICATION INTEGRATION should contain at least one property"); + } + Map reducedProperties = new LinkedHashMap<>(properties); + for (String key : propertiesToUnset) { + if (TYPE_PROPERTY.equalsIgnoreCase(key)) { + throw new DdlException("ALTER AUTHENTICATION INTEGRATION does not allow modifying property 'type'"); + } + reducedProperties.remove(key); + } + return new AuthenticationIntegrationMeta(name, type, reducedProperties, comment); + } + public AuthenticationIntegrationMeta withComment(String newComment) { return new AuthenticationIntegrationMeta(name, type, properties, newComment); } 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 index 0db39dbb4c72b1..6b032192058d65 100644 --- 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 @@ -32,6 +32,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; /** @@ -60,12 +61,16 @@ private void writeUnlock() { } public void createAuthenticationIntegration( - String integrationName, Map properties, String comment) throws DdlException { + String integrationName, boolean ifNotExists, Map properties, String comment) + throws DdlException { AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql(integrationName, properties, comment); writeLock(); try { if (nameToIntegration.containsKey(integrationName)) { + if (ifNotExists) { + return; + } throw new DdlException("Authentication integration " + integrationName + " already exists"); } nameToIntegration.put(integrationName, meta); @@ -88,6 +93,19 @@ public void alterAuthenticationIntegrationProperties( } } + public void alterAuthenticationIntegrationUnsetProperties( + String integrationName, Set propertiesToUnset) throws DdlException { + writeLock(); + try { + AuthenticationIntegrationMeta current = getOrThrow(integrationName); + AuthenticationIntegrationMeta updated = current.withUnsetProperties(propertiesToUnset); + nameToIntegration.put(integrationName, updated); + Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); + } finally { + writeUnlock(); + } + } + public void alterAuthenticationIntegrationComment(String integrationName, String comment) throws DdlException { writeLock(); try { 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 db981250052281..57f665693bfc5b 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 @@ -2120,6 +2120,23 @@ public Map visitPropertyItemList(PropertyItemListContext ctx) { return propertiesMap.build(); } + @Override + public Set visitPropertyKeyList(DorisParser.PropertyKeyListContext ctx) { + if (ctx == null || ctx.keys == null) { + return ImmutableSet.of(); + } + ImmutableSet.Builder propertyKeys = ImmutableSet.builder(); + for (PropertyKeyContext propertyKey : ctx.keys) { + propertyKeys.add(parsePropertyKey(propertyKey)); + } + return propertyKeys.build(); + } + + @Override + public Set visitPropertyKeyClause(DorisParser.PropertyKeyClauseContext ctx) { + return ctx == null ? ImmutableSet.of() : visitPropertyKeyList(ctx.propertyKeyList()); + } + @Override public BrokerDesc visitWithRemoteStorageSystem(WithRemoteStorageSystemContext ctx) { BrokerDesc brokerDesc = null; @@ -5041,8 +5058,8 @@ private String parsePropertyValue(PropertyValueContext item) { return item.getText(); } - private boolean containsPropertyKeyIgnoreCase(Map properties, String expectedKey) { - for (String key : properties.keySet()) { + private boolean containsPropertyKeyIgnoreCase(Iterable propertyKeys, String expectedKey) { + for (String key : propertyKeys) { if (key.equalsIgnoreCase(expectedKey)) { return true; } @@ -7048,13 +7065,14 @@ public LogicalPlan visitCreateCatalog(CreateCatalogContext ctx) { @Override public LogicalPlan visitCreateAuthenticationIntegration( DorisParser.CreateAuthenticationIntegrationContext ctx) { + boolean ifNotExists = ctx.IF() != null; String integrationName = stripQuotes(ctx.integrationName.getText()); - Map properties = Maps.newHashMap(visitPropertyItemList(ctx.propertyItemList())); - if (!containsPropertyKeyIgnoreCase(properties, "type")) { + Map 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, properties, comment); + return new CreateAuthenticationIntegrationCommand(integrationName, ifNotExists, properties, comment); } @Override @@ -7097,14 +7115,26 @@ public LogicalPlan visitAlterCatalogProperties(AlterCatalogPropertiesContext ctx public LogicalPlan visitAlterAuthenticationIntegrationProperties( DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) { String integrationName = stripQuotes(ctx.integrationName.getText()); - Map properties = Maps.newHashMap(visitPropertyItemList(ctx.propertyItemList())); - if (containsPropertyKeyIgnoreCase(properties, "type")) { + Map 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 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) { 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 index 67fefc2e4b98c7..2955d43dc54030 100644 --- 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 @@ -29,8 +29,10 @@ 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. @@ -39,38 +41,48 @@ public class AlterAuthenticationIntegrationCommand extends AlterCommand implemen /** alter action. */ public enum AlterType { SET_PROPERTIES, + UNSET_PROPERTIES, SET_COMMENT } private final String integrationName; private final AlterType alterType; private final Map properties; + private final Set unsetProperties; private final String comment; private AlterAuthenticationIntegrationCommand(String integrationName, AlterType alterType, - Map properties, String comment) { + Map properties, Set 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 properties) { return new AlterAuthenticationIntegrationCommand( - integrationName, AlterType.SET_PROPERTIES, properties, null); + integrationName, AlterType.SET_PROPERTIES, properties, Collections.emptySet(), null); + } + + public static AlterAuthenticationIntegrationCommand forUnsetProperties(String integrationName, + Set 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(), comment); + integrationName, AlterType.SET_COMMENT, Collections.emptyMap(), Collections.emptySet(), comment); } @Override public R accept(PlanVisitor visitor, C context) { - return visitor.visitCommand(this, context); + return visitor.visitAlterAuthenticationIntegrationCommand(this, context); } @Override @@ -83,6 +95,10 @@ public void doRun(ConnectContext ctx, StmtExecutor executor) throws Exception { Env.getCurrentEnv().getAuthenticationIntegrationMgr() .alterAuthenticationIntegrationProperties(integrationName, properties); return; + case UNSET_PROPERTIES: + Env.getCurrentEnv().getAuthenticationIntegrationMgr() + .alterAuthenticationIntegrationUnsetProperties(integrationName, unsetProperties); + return; case SET_COMMENT: Env.getCurrentEnv().getAuthenticationIntegrationMgr() .alterAuthenticationIntegrationComment(integrationName, comment); @@ -109,6 +125,10 @@ public Map getProperties() { return properties; } + public Set 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 index b837ed91a47dc6..af0531eb87172c 100644 --- 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 @@ -37,13 +37,16 @@ */ public class CreateAuthenticationIntegrationCommand extends Command implements ForwardWithSync, NeedAuditEncryption { private final String integrationName; + private final boolean ifNotExists; private final Map properties; private final String comment; - public CreateAuthenticationIntegrationCommand(String integrationName, + /** Constructor. */ + public CreateAuthenticationIntegrationCommand(String integrationName, boolean ifNotExists, Map 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; @@ -51,7 +54,7 @@ public CreateAuthenticationIntegrationCommand(String integrationName, @Override public R accept(PlanVisitor visitor, C context) { - return visitor.visitCommand(this, context); + return visitor.visitCreateAuthenticationIntegrationCommand(this, context); } @Override @@ -60,7 +63,7 @@ public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } Env.getCurrentEnv().getAuthenticationIntegrationMgr() - .createAuthenticationIntegration(integrationName, properties, comment); + .createAuthenticationIntegration(integrationName, ifNotExists, properties, comment); } @Override @@ -77,6 +80,10 @@ public String getIntegrationName() { return integrationName; } + public boolean isSetIfNotExists() { + return ifNotExists; + } + public Map getProperties() { return properties; } 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 index 112148423bc6ea..aa041d44678e3c 100644 --- 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 @@ -43,7 +43,7 @@ public DropAuthenticationIntegrationCommand(boolean ifExists, String integration @Override public R accept(PlanVisitor visitor, C context) { - return visitor.visitCommand(this, context); + return visitor.visitDropAuthenticationIntegrationCommand(this, context); } @Override 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 743dd9dbbb5568..00b64230deb34a 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.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.CleanQueryStatsCommand; import org.apache.doris.nereids.trees.plans.commands.Command; import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand; +import org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand; import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand; import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand; import org.apache.doris.nereids.trees.plans.commands.CreateDictionaryCommand; @@ -103,6 +105,7 @@ import org.apache.doris.nereids.trees.plans.commands.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; @@ -515,6 +518,11 @@ default R visitCreateCatalogCommand(CreateCatalogCommand createCatalogCommand, 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); } @@ -543,6 +551,11 @@ default R visitDropCatalogCommand(DropCatalogCommand dropCatalogCommand, C conte 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); } @@ -858,6 +871,11 @@ default R visitAlterCatalogPropertiesCommand(AlterCatalogPropertiesCommand alter 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 index c9a05d13ef2bad..a2f2015cdc2a8f 100644 --- 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 @@ -31,7 +31,7 @@ * Drop log for AUTHENTICATION INTEGRATION. */ public class DropAuthenticationIntegrationOperationLog implements Writable { - @SerializedName(value = "integrationName") + @SerializedName(value = "in") private String integrationName; public DropAuthenticationIntegrationOperationLog(String integrationName) { 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 6d1f4afa36a7cb..979963bb405175 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,10 +40,11 @@ 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", "authenticationIntegrations", "policy", + "plugins", "deleteHandler", "sqlBlockRule", "policy", "globalFunction", "workloadGroups", "binlogs", "resourceGroups", "AnalysisMgrV2", "AsyncJobManager", "workloadSchedPolicy", - "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", "KeyManagerStore"); + "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", "KeyManagerStore", + "authenticationIntegrations"); // The modules in `CloudEnv`. public static final ImmutableList CLOUD_MODULE_NAMES = ImmutableList.of("cloudWarmUpJob"); diff --git a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java index 196e0541b1753b..b11268c7cc3a87 100644 --- 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 @@ -29,7 +29,9 @@ 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 { @@ -41,6 +43,12 @@ private static Map map(String... kvs) { return result; } + private static Set set(String... keys) { + Set result = new LinkedHashSet<>(); + Collections.addAll(result, keys); + return result; + } + @Test public void testFromCreateSqlSuccessAndTypeFiltered() throws Exception { Map properties = new LinkedHashMap<>(); @@ -113,6 +121,26 @@ public void testWithAlterProperties() throws Exception { () -> meta.withAlterProperties(map("TYPE", "oidc"))); } + @Test + public void testWithUnsetProperties() throws Exception { + AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql( + "corp_ldap", + map("type", "ldap", + "ldap.server", "ldap://old", + "ldap.base_dn", "dc=example,dc=com"), + "old comment"); + + AuthenticationIntegrationMeta altered = meta.withUnsetProperties(set("ldap.base_dn")); + Assertions.assertEquals("ldap", altered.getType()); + Assertions.assertFalse(altered.getProperties().containsKey("ldap.base_dn")); + Assertions.assertEquals("ldap://old", altered.getProperties().get("ldap.server")); + + Assertions.assertThrows(DdlException.class, + () -> meta.withUnsetProperties(Collections.emptySet())); + Assertions.assertThrows(DdlException.class, + () -> meta.withUnsetProperties(set("TYPE"))); + } + @Test public void testWriteReadRoundTrip() throws IOException, DdlException { AuthenticationIntegrationMeta meta = AuthenticationIntegrationMeta.fromCreateSql( 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 index 479532d321acb5..18f1dd530c72ec 100644 --- 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 @@ -32,8 +32,11 @@ 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 { @@ -51,6 +54,12 @@ private static Map map(String... kvs) { return result; } + private static Set set(String... keys) { + Set result = new LinkedHashSet<>(); + Collections.addAll(result, keys); + return result; + } + @Test public void testCreateAlterDropFlow() throws Exception { new Expectations() { @@ -78,8 +87,9 @@ public void testCreateAlterDropFlow() throws Exception { Map 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", createProperties, "comment"); + mgr.createAuthenticationIntegration("corp_ldap", false, createProperties, "comment"); AuthenticationIntegrationMeta created = mgr.getAuthenticationIntegrations().get("corp_ldap"); Assertions.assertNotNull(created); Assertions.assertEquals("ldap", created.getType()); @@ -89,6 +99,10 @@ public void testCreateAlterDropFlow() throws Exception { Assertions.assertEquals("ldap://127.0.0.1:1389", mgr.getAuthenticationIntegrations().get("corp_ldap").getProperties().get("ldap.server")); + mgr.alterAuthenticationIntegrationUnsetProperties("corp_ldap", set("ldap.admin_password")); + Assertions.assertFalse(mgr.getAuthenticationIntegrations() + .get("corp_ldap").getProperties().containsKey("ldap.admin_password")); + mgr.alterAuthenticationIntegrationComment("corp_ldap", "new comment"); Assertions.assertEquals("new comment", mgr.getAuthenticationIntegrations().get("corp_ldap").getComment()); @@ -117,12 +131,14 @@ public void testCreateDuplicateAndDropIfExists() throws Exception { }; AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); - mgr.createAuthenticationIntegration("corp_ldap", map( + mgr.createAuthenticationIntegration("corp_ldap", false, map( "type", "ldap", "ldap.server", "ldap://127.0.0.1:389"), null); Assertions.assertThrows(DdlException.class, - () -> mgr.createAuthenticationIntegration("corp_ldap", map("type", "ldap"), null)); + () -> mgr.createAuthenticationIntegration("corp_ldap", false, map("type", "ldap"), null)); + Assertions.assertDoesNotThrow( + () -> mgr.createAuthenticationIntegration("corp_ldap", true, map("type", "ldap"), null)); Assertions.assertDoesNotThrow(() -> mgr.dropAuthenticationIntegration("not_exist", true)); Assertions.assertThrows(DdlException.class, @@ -134,6 +150,8 @@ public void testAlterNotExistThrows() { AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr(); Assertions.assertThrows(DdlException.class, () -> mgr.alterAuthenticationIntegrationProperties("not_exist", map("k", "v"))); + Assertions.assertThrows(DdlException.class, + () -> mgr.alterAuthenticationIntegrationUnsetProperties("not_exist", set("k"))); Assertions.assertThrows(DdlException.class, () -> mgr.alterAuthenticationIntegrationComment("not_exist", "comment")); } 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 index cb4344ffda036f..2a01a2372095f7 100644 --- 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 @@ -32,13 +32,14 @@ public class AuthenticationIntegrationParserTest { @Test public void testCreateAuthenticationIntegrationParse() { - LogicalPlan plan = parser.parseSingle("CREATE AUTHENTICATION INTEGRATION corp_ldap " - + "WITH PROPERTIES ('type'='ldap', 'ldap.server'='ldap://127.0.0.1:389') " + 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()); @@ -48,7 +49,7 @@ public void testCreateAuthenticationIntegrationParse() { public void testCreateAuthenticationIntegrationRequireType() { Assertions.assertThrows(ParseException.class, () -> parser.parseSingle( "CREATE AUTHENTICATION INTEGRATION corp_ldap " - + "WITH PROPERTIES ('ldap.server'='ldap://127.0.0.1:389')")); + + "PROPERTIES ('ldap.server'='ldap://127.0.0.1:389')")); } @Test @@ -65,6 +66,16 @@ public void testAlterAuthenticationIntegrationParse() { 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); @@ -80,6 +91,8 @@ public void testAlterAuthenticationIntegrationParse() { 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 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 index 486fe31679fc35..ed1a70b03d5815 100644 --- 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 @@ -30,8 +30,11 @@ 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 { @@ -55,10 +58,16 @@ private static Map map(String... kvs) { return result; } + private static Set set(String... keys) { + Set result = new LinkedHashSet<>(); + Collections.addAll(result, keys); + return result; + } + @Test public void testCreateCommandRunAndDenied() throws Exception { CreateAuthenticationIntegrationCommand createCommand = - new CreateAuthenticationIntegrationCommand("corp_ldap", + new CreateAuthenticationIntegrationCommand("corp_ldap", false, map("type", "ldap", "ldap.server", "ldap://127.0.0.1:389"), "comment"); new Expectations() { @@ -78,7 +87,8 @@ public void testCreateCommandRunAndDenied() throws Exception { minTimes = 0; result = authenticationIntegrationMgr; - authenticationIntegrationMgr.createAuthenticationIntegration(anyString, (Map) any, anyString); + authenticationIntegrationMgr.createAuthenticationIntegration( + anyString, anyBoolean, (Map) any, anyString); times = 1; } }; @@ -110,6 +120,9 @@ public void testAlterCommandRun() throws Exception { AlterAuthenticationIntegrationCommand setPropertiesCommand = AlterAuthenticationIntegrationCommand.forSetProperties( "corp_ldap", map("ldap.server", "ldap://127.0.0.1:1389")); + AlterAuthenticationIntegrationCommand unsetPropertiesCommand = + AlterAuthenticationIntegrationCommand.forUnsetProperties( + "corp_ldap", set("ldap.server")); AlterAuthenticationIntegrationCommand setCommentCommand = AlterAuthenticationIntegrationCommand.forSetComment("corp_ldap", "new comment"); @@ -131,7 +144,12 @@ public void testAlterCommandRun() throws Exception { minTimes = 0; result = authenticationIntegrationMgr; - authenticationIntegrationMgr.alterAuthenticationIntegrationProperties(anyString, (Map) any); + authenticationIntegrationMgr.alterAuthenticationIntegrationProperties( + anyString, (Map) any); + times = 1; + + authenticationIntegrationMgr.alterAuthenticationIntegrationUnsetProperties( + anyString, (Set) any); times = 1; authenticationIntegrationMgr.alterAuthenticationIntegrationComment(anyString, anyString); @@ -140,8 +158,10 @@ public void testAlterCommandRun() throws Exception { }; 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()); } From d77c62b43525d24f6038ea7b58f0b54b43b0cd2b Mon Sep 17 00:00:00 2001 From: guoqiang Date: Wed, 4 Mar 2026 18:51:13 +0800 Subject: [PATCH 4/8] fix --- .../AuthenticationIntegrationMgr.java | 23 +++++++++++++------ .../java/org/apache/doris/catalog/Env.java | 14 ++++++++--- 2 files changed, 27 insertions(+), 10 deletions(-) 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 index 6b032192058d65..138b325674491a 100644 --- 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 @@ -17,7 +17,6 @@ package org.apache.doris.authentication; -import org.apache.doris.catalog.Env; import org.apache.doris.common.DdlException; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; @@ -74,7 +73,9 @@ public void createAuthenticationIntegration( throw new DdlException("Authentication integration " + integrationName + " already exists"); } nameToIntegration.put(integrationName, meta); - Env.getCurrentEnv().getEditLog().logCreateAuthenticationIntegration(meta); + // TODO(authentication-integration): Re-enable edit log persistence + // when authentication integration is fully integrated. + // Env.getCurrentEnv().getEditLog().logCreateAuthenticationIntegration(meta); } finally { writeUnlock(); } @@ -87,7 +88,9 @@ public void alterAuthenticationIntegrationProperties( AuthenticationIntegrationMeta current = getOrThrow(integrationName); AuthenticationIntegrationMeta updated = current.withAlterProperties(properties); nameToIntegration.put(integrationName, updated); - Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); + // TODO(authentication-integration): Re-enable edit log persistence + // when authentication integration is fully integrated. + // Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); } finally { writeUnlock(); } @@ -100,7 +103,9 @@ public void alterAuthenticationIntegrationUnsetProperties( AuthenticationIntegrationMeta current = getOrThrow(integrationName); AuthenticationIntegrationMeta updated = current.withUnsetProperties(propertiesToUnset); nameToIntegration.put(integrationName, updated); - Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); + // TODO(authentication-integration): Re-enable edit log persistence + // when authentication integration is fully integrated. + // Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); } finally { writeUnlock(); } @@ -112,7 +117,9 @@ public void alterAuthenticationIntegrationComment(String integrationName, String AuthenticationIntegrationMeta current = getOrThrow(integrationName); AuthenticationIntegrationMeta updated = current.withComment(comment); nameToIntegration.put(integrationName, updated); - Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); + // TODO(authentication-integration): Re-enable edit log persistence + // when authentication integration is fully integrated. + // Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated); } finally { writeUnlock(); } @@ -128,8 +135,10 @@ public void dropAuthenticationIntegration(String integrationName, boolean ifExis throw new DdlException("Authentication integration " + integrationName + " does not exist"); } nameToIntegration.remove(integrationName); - Env.getCurrentEnv().getEditLog().logDropAuthenticationIntegration( - new DropAuthenticationIntegrationOperationLog(integrationName)); + // TODO(authentication-integration): Re-enable edit log persistence + // when authentication integration is fully integrated. + // Env.getCurrentEnv().getEditLog().logDropAuthenticationIntegration( + // new DropAuthenticationIntegrationOperationLog(integrationName)); } finally { writeUnlock(); } 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 49ed49976a9ed2..73f8e705358e7d 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 @@ -2492,8 +2492,13 @@ public long loadSqlBlockRule(DataInputStream in, long checksum) throws IOExcepti } public long loadAuthenticationIntegrations(DataInputStream in, long checksum) throws IOException { - authenticationIntegrationMgr = AuthenticationIntegrationMgr.read(in); - LOG.info("finished replay authentication integrations from image"); + // 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; } @@ -2810,7 +2815,10 @@ public long saveSqlBlockRule(CountingDataOutputStream out, long checksum) throws } public long saveAuthenticationIntegrations(CountingDataOutputStream out, long checksum) throws IOException { - Env.getCurrentEnv().getAuthenticationIntegrationMgr().write(out); + // TODO(authentication-integration): Re-enable image persistence + // when authentication integration is fully integrated. + // Persist an empty manager temporarily. + new AuthenticationIntegrationMgr().write(out); return checksum; } From 1d4dc6680895ab54f1f113089da1fda5830727bb Mon Sep 17 00:00:00 2001 From: guoqiang Date: Wed, 4 Mar 2026 19:14:47 +0800 Subject: [PATCH 5/8] fix --- .../AuthenticationIntegrationMeta.java | 6 +++--- .../AuthenticationIntegrationMgr.java | 2 +- .../LogicalPlanBuilderForEncryption.java | 20 +++++++++---------- .../doris/persist/meta/MetaPersistMethod.java | 17 ++++++++-------- .../persist/meta/PersistMetaModules.java | 6 ++++-- 5 files changed, 27 insertions(+), 24 deletions(-) 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 index 687d5baff43dda..6fb64a55a95471 100644 --- 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 @@ -39,13 +39,13 @@ public class AuthenticationIntegrationMeta implements Writable { public static final String TYPE_PROPERTY = "type"; - @SerializedName(value = "name") + @SerializedName(value = "n") private String name; - @SerializedName(value = "type") + @SerializedName(value = "t") private String type; @SerializedName(value = "p") private Map properties; - @SerializedName(value = "comment") + @SerializedName(value = "c") private String comment; private AuthenticationIntegrationMeta() { 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 index 138b325674491a..6b8218830545bf 100644 --- 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 @@ -40,7 +40,7 @@ public class AuthenticationIntegrationMgr implements Writable { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); - @SerializedName(value = "nameToIntegration") + @SerializedName(value = "nTi") private Map nameToIntegration = new LinkedHashMap<>(); private void readLock() { 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 49aeb374dac1a3..0555ea108dc3ec 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 @@ -135,11 +135,11 @@ public LogicalPlan visitCreateStorageVault(DorisParser.CreateStorageVaultContext // create authentication integration clause @Override public LogicalPlan visitCreateAuthenticationIntegration(DorisParser.CreateAuthenticationIntegrationContext ctx) { - if (ctx.propertyItemList() != null) { - DorisParser.PropertyItemListContext propertyItemListContext = ctx.propertyItemList(); - encryptProperty(visitPropertyItemList(propertyItemListContext), - propertyItemListContext.start.getStartIndex(), - propertyItemListContext.stop.getStopIndex()); + 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); } @@ -160,11 +160,11 @@ public LogicalPlan visitAlterStorageVault(DorisParser.AlterStorageVaultContext c @Override public LogicalPlan visitAlterAuthenticationIntegrationProperties( DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) { - if (ctx.propertyItemList() != null) { - DorisParser.PropertyItemListContext propertyItemListContext = ctx.propertyItemList(); - encryptProperty(visitPropertyItemList(propertyItemListContext), - propertyItemListContext.start.getStartIndex(), - propertyItemListContext.stop.getStopIndex()); + 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); } 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 e6699ef1a10545..ea3585d493bd8c 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,14 +186,15 @@ public static MetaPersistMethod create(String name) throws NoSuchMethodException metaPersistMethod.writeMethod = Env.class.getDeclaredMethod("saveSqlBlockRule", CountingDataOutputStream.class, long.class); break; - case "authenticationIntegrations": - metaPersistMethod.readMethod = - Env.class.getDeclaredMethod("loadAuthenticationIntegrations", DataInputStream.class, - long.class); - metaPersistMethod.writeMethod = - Env.class.getDeclaredMethod("saveAuthenticationIntegrations", 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 979963bb405175..44809035406666 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 @@ -43,8 +43,10 @@ public class PersistMetaModules { "plugins", "deleteHandler", "sqlBlockRule", "policy", "globalFunction", "workloadGroups", "binlogs", "resourceGroups", "AnalysisMgrV2", "AsyncJobManager", "workloadSchedPolicy", - "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", "KeyManagerStore", - "authenticationIntegrations"); + "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", "KeyManagerStore" + // TODO: Re-enable "authenticationIntegrations" after persistence requirements are confirmed. + // , "authenticationIntegrations" + ); // The modules in `CloudEnv`. public static final ImmutableList CLOUD_MODULE_NAMES = ImmutableList.of("cloudWarmUpJob"); From 47666ee89a67f758de8719fd4fa3f862b8670d29 Mon Sep 17 00:00:00 2001 From: guoqiang Date: Wed, 4 Mar 2026 19:40:30 +0800 Subject: [PATCH 6/8] fix --- .../main/antlr4/org/apache/doris/nereids/DorisParser.g4 | 6 +----- .../apache/doris/nereids/parser/LogicalPlanBuilder.java | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) 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 3ea0f718623171..5d0172fcdf5e08 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 @@ -1464,17 +1464,13 @@ propertyClause ; propertyKeyClause - : PROPERTIES LEFT_PAREN propertyKeyList RIGHT_PAREN + : PROPERTIES LEFT_PAREN keys+=propertyKey (COMMA keys+=propertyKey)* RIGHT_PAREN ; propertyItemList : properties+=propertyItem (COMMA properties+=propertyItem)* ; -propertyKeyList - : keys+=propertyKey (COMMA keys+=propertyKey)* - ; - propertyItem : key=propertyKey EQ value=propertyValue ; 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 57f665693bfc5b..b9b3bf32a6ea88 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 @@ -2121,7 +2121,7 @@ public Map visitPropertyItemList(PropertyItemListContext ctx) { } @Override - public Set visitPropertyKeyList(DorisParser.PropertyKeyListContext ctx) { + public Set visitPropertyKeyClause(DorisParser.PropertyKeyClauseContext ctx) { if (ctx == null || ctx.keys == null) { return ImmutableSet.of(); } @@ -2132,11 +2132,6 @@ public Set visitPropertyKeyList(DorisParser.PropertyKeyListContext ctx) return propertyKeys.build(); } - @Override - public Set visitPropertyKeyClause(DorisParser.PropertyKeyClauseContext ctx) { - return ctx == null ? ImmutableSet.of() : visitPropertyKeyList(ctx.propertyKeyList()); - } - @Override public BrokerDesc visitWithRemoteStorageSystem(WithRemoteStorageSystemContext ctx) { BrokerDesc brokerDesc = null; From 3ec0c30ac75fbbadb87cad0d35351e6222378668 Mon Sep 17 00:00:00 2001 From: guoqiang Date: Thu, 5 Mar 2026 10:53:30 +0800 Subject: [PATCH 7/8] fix --- .../test_authentication_integration_auth.groovy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy index 3405d6a818fa4c..085bbfa25dd308 100644 --- a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy +++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy @@ -1,16 +1,16 @@ // 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 +// distributed 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 +// 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 +// "AS IS" BASIS, OUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. @@ -25,14 +25,14 @@ suite("test_authentication_integration_auth", "p0,auth") { test { sql """ CREATE AUTHENTICATION INTEGRATION ${integrationName} - WITH PROPERTIES ('ldap.server'='ldap://127.0.0.1:389') + PROPERTIES ('ldap.server'='ldap://127.0.0.1:389') """ exception "Property 'type' is required" } sql """ CREATE AUTHENTICATION INTEGRATION ${integrationName} - WITH PROPERTIES ( + PROPERTIES ( 'type'='ldap', 'ldap.server'='ldap://127.0.0.1:389', 'ldap.admin_password'='123456' @@ -43,7 +43,7 @@ suite("test_authentication_integration_auth", "p0,auth") { test { sql """ CREATE AUTHENTICATION INTEGRATION ${integrationName} - WITH PROPERTIES ('type'='ldap', 'ldap.server'='ldap://127.0.0.1:1389') + PROPERTIES ('type'='ldap', 'ldap.server'='ldap://127.0.0.1:1389') """ exception "already exists" } From bd1db37752da3e552183132e0f8d2e427ab90dd7 Mon Sep 17 00:00:00 2001 From: guoqiang Date: Thu, 5 Mar 2026 11:03:24 +0800 Subject: [PATCH 8/8] fix --- .../auth_p0/test_authentication_integration_auth.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy index 085bbfa25dd308..c65ac577574821 100644 --- a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy +++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy @@ -1,16 +1,16 @@ // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file -// distributed this work for additional information +// 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 -// the License. You may obtain a copy of the License at +// 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, OUT WARRANTIES OR CONDITIONS OF ANY +// "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.