This is an automated email from the ASF dual-hosted git repository.
kirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new e2e5159b98e [feature](fe) Add information_schema role mappings table
(#62077)
e2e5159b98e is described below
commit e2e5159b98e9b85ba1e0ec37ae93988867303c6d
Author: Calvin Kirs <[email protected]>
AuthorDate: Fri Apr 10 12:40:25 2026 +0800
[feature](fe) Add information_schema role mappings table (#62077)
[[feature](fe) Add information_schema role mappings
table](https://github.com/apache/doris/commit/399d8b05fc9d8f73bc9abee9a5b46dbe9165cd4f)
https://github.com/apache/doris/issues/60361
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary: Add information_schema.role_mappings so administrators
can inspect authentication integration role mapping definitions and
audit fields through SQL.
### Release note
Add information_schema.role_mappings for inspecting role mapping
metadata.
### Check List (For Author)
- Test: FE Unit Test; Regression test attempted
- FE Unit Test: ./run-fe-ut.sh --run
org.apache.doris.service.FrontendServiceImplTest
- FE Unit Test: ./run-fe-ut.sh --run
org.apache.doris.catalog.SchemaTableTest
- Regression test: ./run-regression-test.sh --run -d auth_p0 -s
test_role_mapping_system_table (blocked locally because the default
127.0.0.1:9030 cluster did not include CREATE ROLE MAPPING syntax)
- Behavior changed: Yes (adds information_schema.role_mappings)
- Does this need documentation: No
---
.../schema_role_mappings_scanner.cpp | 145 +++++++++++++++++++++
.../schema_role_mappings_scanner.h | 53 ++++++++
be/src/information_schema/schema_scanner.cpp | 3 +
.../schema_role_mappings_scanner_test.cpp | 40 ++++++
.../org/apache/doris/analysis/SchemaTableType.java | 3 +-
.../java/org/apache/doris/catalog/SchemaTable.java | 11 ++
.../doris/tablefunction/MetadataGenerator.java | 77 +++++++++++
.../org/apache/doris/catalog/SchemaTableTest.java | 13 ++
.../doris/service/FrontendServiceImplTest.java | 124 ++++++++++++++++++
gensrc/thrift/Descriptors.thrift | 1 +
gensrc/thrift/FrontendService.thrift | 1 +
.../auth_p0/test_role_mapping_system_table.groovy | 123 +++++++++++++++++
12 files changed, 593 insertions(+), 1 deletion(-)
diff --git a/be/src/information_schema/schema_role_mappings_scanner.cpp
b/be/src/information_schema/schema_role_mappings_scanner.cpp
new file mode 100644
index 00000000000..c9582e26558
--- /dev/null
+++ b/be/src/information_schema/schema_role_mappings_scanner.cpp
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "information_schema/schema_role_mappings_scanner.h"
+
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/data_type/data_type_factory.hpp"
+#include "core/string_ref.h"
+#include "information_schema/schema_helper.h"
+#include "runtime/exec_env.h"
+#include "runtime/runtime_state.h"
+#include "util/client_cache.h"
+#include "util/thrift_rpc_helper.h"
+
+namespace doris {
+#include "common/compile_check_begin.h"
+
+std::vector<SchemaScanner::ColumnDesc>
SchemaRoleMappingsScanner::_s_tbls_columns = {
+ {"NAME", TYPE_VARCHAR, sizeof(StringRef), true},
+ {"INTEGRATION_NAME", TYPE_VARCHAR, sizeof(StringRef), true},
+ {"RULES", TYPE_STRING, sizeof(StringRef), true},
+ {"COMMENT", TYPE_STRING, sizeof(StringRef), true},
+ {"CREATE_USER", TYPE_STRING, sizeof(StringRef), true},
+ {"CREATE_TIME", TYPE_STRING, sizeof(StringRef), true},
+ {"ALTER_USER", TYPE_STRING, sizeof(StringRef), true},
+ {"MODIFY_TIME", TYPE_STRING, sizeof(StringRef), true},
+};
+
+SchemaRoleMappingsScanner::SchemaRoleMappingsScanner()
+ : SchemaScanner(_s_tbls_columns, TSchemaTableType::SCH_ROLE_MAPPINGS)
{}
+
+Status SchemaRoleMappingsScanner::start(RuntimeState* state) {
+ if (!_is_init) {
+ return Status::InternalError("used before initialized.");
+ }
+ _block_rows_limit = state->batch_size();
+ _rpc_timeout_ms = state->execution_timeout() * 1000;
+ return Status::OK();
+}
+
+Status SchemaRoleMappingsScanner::_get_role_mappings_block_from_fe() {
+ TNetworkAddress master_addr =
ExecEnv::GetInstance()->cluster_info()->master_fe_addr;
+
+ TSchemaTableRequestParams schema_table_request_params;
+ for (int i = 0; i < _s_tbls_columns.size(); i++) {
+ schema_table_request_params.__isset.columns_name = true;
+
schema_table_request_params.columns_name.emplace_back(_s_tbls_columns[i].name);
+ }
+
schema_table_request_params.__set_current_user_ident(*_param->common_param->current_user_ident);
+ if (_param->common_param->frontend_conjuncts) {
+ schema_table_request_params.__set_frontend_conjuncts(
+ *_param->common_param->frontend_conjuncts);
+ }
+
+ TFetchSchemaTableDataRequest request;
+ request.__set_schema_table_name(TSchemaTableName::ROLE_MAPPINGS);
+ request.__set_schema_table_params(schema_table_request_params);
+
+ TFetchSchemaTableDataResult result;
+ RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>(
+ master_addr.hostname, master_addr.port,
+ [&request, &result](FrontendServiceConnection& client) {
+ client->fetchSchemaTableData(result, request);
+ },
+ _rpc_timeout_ms));
+
+ Status status(Status::create(result.status));
+ if (!status.ok()) {
+ LOG(WARNING) << "fetch role mappings from FE failed, errmsg=" <<
status;
+ return status;
+ }
+
+ _role_mappings_block = Block::create_unique();
+ for (int i = 0; i < _s_tbls_columns.size(); ++i) {
+ auto data_type =
+
DataTypeFactory::instance().create_data_type(_s_tbls_columns[i].type, true);
+
_role_mappings_block->insert(ColumnWithTypeAndName(data_type->create_column(),
data_type,
+
_s_tbls_columns[i].name));
+ }
+ _role_mappings_block->reserve(_block_rows_limit);
+
+ std::vector<TRow> result_data = std::move(result.data_batch);
+ if (!result_data.empty()) {
+ auto col_size = result_data[0].column_value.size();
+ if (col_size != _s_tbls_columns.size()) {
+ return Status::InternalError<false>("role mappings schema is not
match for FE and BE");
+ }
+ }
+
+ for (int i = 0; i < result_data.size(); i++) {
+ const TRow& row = result_data[i];
+ for (int j = 0; j < _s_tbls_columns.size(); j++) {
+ RETURN_IF_ERROR(insert_block_column(row.column_value[j], j,
_role_mappings_block.get(),
+ _s_tbls_columns[j].type));
+ }
+ }
+ return Status::OK();
+}
+
+Status SchemaRoleMappingsScanner::get_next_block_internal(Block* block, bool*
eos) {
+ if (!_is_init) {
+ return Status::InternalError("Used before initialized.");
+ }
+
+ if (nullptr == block || nullptr == eos) {
+ return Status::InternalError("input pointer is nullptr.");
+ }
+
+ if (_role_mappings_block == nullptr) {
+ RETURN_IF_ERROR(_get_role_mappings_block_from_fe());
+ _total_rows = static_cast<int>(_role_mappings_block->rows());
+ }
+
+ if (_row_idx == _total_rows) {
+ *eos = true;
+ return Status::OK();
+ }
+
+ int current_batch_rows = std::min(_block_rows_limit, _total_rows -
_row_idx);
+ MutableBlock mblock = MutableBlock::build_mutable_block(block);
+ RETURN_IF_ERROR(mblock.add_rows(_role_mappings_block.get(), _row_idx,
current_batch_rows));
+ _row_idx += current_batch_rows;
+
+ *eos = _row_idx == _total_rows;
+ return Status::OK();
+}
+
+#include "common/compile_check_end.h"
+} // namespace doris
diff --git a/be/src/information_schema/schema_role_mappings_scanner.h
b/be/src/information_schema/schema_role_mappings_scanner.h
new file mode 100644
index 00000000000..959bbd921f6
--- /dev/null
+++ b/be/src/information_schema/schema_role_mappings_scanner.h
@@ -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.
+
+#pragma once
+
+#include <gen_cpp/FrontendService_types.h>
+
+#include <vector>
+
+#include "common/status.h"
+#include "information_schema/schema_scanner.h"
+
+namespace doris {
+class RuntimeState;
+class Block;
+
+class SchemaRoleMappingsScanner : public SchemaScanner {
+ ENABLE_FACTORY_CREATOR(SchemaRoleMappingsScanner);
+
+public:
+ SchemaRoleMappingsScanner();
+ ~SchemaRoleMappingsScanner() override = default;
+
+ Status start(RuntimeState* state) override;
+ Status get_next_block_internal(Block* block, bool* eos) override;
+
+ static std::vector<SchemaScanner::ColumnDesc> _s_tbls_columns;
+
+private:
+ Status _get_role_mappings_block_from_fe();
+
+ int _block_rows_limit = 4096;
+ int _row_idx = 0;
+ int _total_rows = 0;
+ int _rpc_timeout_ms = 3000;
+ std::unique_ptr<Block> _role_mappings_block = nullptr;
+};
+
+} // namespace doris
diff --git a/be/src/information_schema/schema_scanner.cpp
b/be/src/information_schema/schema_scanner.cpp
index 25a5fd07c2f..981956330ce 100644
--- a/be/src/information_schema/schema_scanner.cpp
+++ b/be/src/information_schema/schema_scanner.cpp
@@ -67,6 +67,7 @@
#include "information_schema/schema_partitions_scanner.h"
#include "information_schema/schema_processlist_scanner.h"
#include "information_schema/schema_profiling_scanner.h"
+#include "information_schema/schema_role_mappings_scanner.h"
#include "information_schema/schema_routine_load_job_scanner.h"
#include "information_schema/schema_rowsets_scanner.h"
#include "information_schema/schema_schema_privileges_scanner.h"
@@ -268,6 +269,8 @@ std::unique_ptr<SchemaScanner>
SchemaScanner::create(TSchemaTableType::type type
return SchemaFileCacheInfoScanner::create_unique();
case TSchemaTableType::SCH_AUTHENTICATION_INTEGRATIONS:
return SchemaAuthenticationIntegrationsScanner::create_unique();
+ case TSchemaTableType::SCH_ROLE_MAPPINGS:
+ return SchemaRoleMappingsScanner::create_unique();
case TSchemaTableType::SCH_TABLE_STREAMS:
return SchemaTableStreamsScanner::create_unique();
case TSchemaTableType::SCH_TABLE_STREAM_CONSUMPTION:
diff --git a/be/test/exec/schema_scanner/schema_role_mappings_scanner_test.cpp
b/be/test/exec/schema_scanner/schema_role_mappings_scanner_test.cpp
new file mode 100644
index 00000000000..82e302b0204
--- /dev/null
+++ b/be/test/exec/schema_scanner/schema_role_mappings_scanner_test.cpp
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gen_cpp/Descriptors_types.h>
+#include <gtest/gtest.h>
+
+#include "information_schema/schema_scanner.h"
+
+namespace doris {
+
+TEST(SchemaRoleMappingsScannerTest, test_create_role_mappings_scanner) {
+ auto scanner = SchemaScanner::create(TSchemaTableType::SCH_ROLE_MAPPINGS);
+ ASSERT_NE(nullptr, scanner);
+ EXPECT_EQ(TSchemaTableType::SCH_ROLE_MAPPINGS, scanner->type());
+ ASSERT_EQ(8, scanner->get_column_desc().size());
+ EXPECT_STREQ("NAME", scanner->get_column_desc()[0].name);
+ EXPECT_STREQ("INTEGRATION_NAME", scanner->get_column_desc()[1].name);
+ EXPECT_STREQ("RULES", scanner->get_column_desc()[2].name);
+ EXPECT_STREQ("COMMENT", scanner->get_column_desc()[3].name);
+ EXPECT_STREQ("CREATE_USER", scanner->get_column_desc()[4].name);
+ EXPECT_STREQ("CREATE_TIME", scanner->get_column_desc()[5].name);
+ EXPECT_STREQ("ALTER_USER", scanner->get_column_desc()[6].name);
+ EXPECT_STREQ("MODIFY_TIME", scanner->get_column_desc()[7].name);
+}
+
+} // namespace doris
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
index 06d98182dd9..8807381c2cc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
@@ -121,7 +121,8 @@ public enum SchemaTableType {
SCH_TABLE_STREAM_CONSUMPTION("TABLE_STREAM_CONSUMPTION",
"TABLE_STREAM_CONSUMPTION",
TSchemaTableType.SCH_TABLE_STREAM_CONSUMPTION),
SCH_BE_COMPACTION_TASKS("BE_COMPACTION_TASKS", "BE_COMPACTION_TASKS",
- TSchemaTableType.SCH_BE_COMPACTION_TASKS);
+ TSchemaTableType.SCH_BE_COMPACTION_TASKS),
+ SCH_ROLE_MAPPINGS("ROLE_MAPPINGS", "ROLE_MAPPINGS",
TSchemaTableType.SCH_ROLE_MAPPINGS);
private static final String dbName = "INFORMATION_SCHEMA";
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
index 371d43f878a..4770c780a29 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
@@ -847,6 +847,17 @@ public class SchemaTable extends Table {
.column("ALTER_USER",
ScalarType.createStringType())
.column("MODIFY_TIME",
ScalarType.createStringType())
.build()))
+ .put("role_mappings",
+ new SchemaTable(SystemIdGenerator.getNextId(),
"role_mappings", TableType.SCHEMA,
+ builder().column("NAME", ScalarType.createVarchar(256))
+ .column("INTEGRATION_NAME",
ScalarType.createVarchar(256))
+ .column("RULES", ScalarType.createStringType())
+ .column("COMMENT", ScalarType.createStringType())
+ .column("CREATE_USER",
ScalarType.createStringType())
+ .column("CREATE_TIME",
ScalarType.createStringType())
+ .column("ALTER_USER",
ScalarType.createStringType())
+ .column("MODIFY_TIME",
ScalarType.createStringType())
+ .build()))
.put("table_streams",
new SchemaTable(SystemIdGenerator.getNextId(),
"table_streams", TableType.SCHEMA,
builder().column("DB_NAME",
ScalarType.createVarchar(NAME_CHAR_LEN))
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
index a91eac1234e..83f3252e84b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
@@ -19,6 +19,7 @@ package org.apache.doris.tablefunction;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.authentication.AuthenticationIntegrationMeta;
+import org.apache.doris.authentication.RoleMappingMeta;
import org.apache.doris.blockrule.SqlBlockRule;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DataProperty;
@@ -44,6 +45,7 @@ import org.apache.doris.catalog.Type;
import org.apache.doris.catalog.View;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.ClientPool;
+import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.Pair;
import org.apache.doris.common.proc.FrontendsProcNode;
import org.apache.doris.common.proc.PartitionsProcDir;
@@ -162,6 +164,8 @@ public class MetadataGenerator {
private static final ImmutableMap<String, Integer>
AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
+ private static final ImmutableMap<String, Integer>
ROLE_MAPPINGS_COLUMN_TO_INDEX;
+
private static final ImmutableMap<String, Integer>
TABLE_STREAMS_COLUMN_TO_INDEX;
private static final ImmutableMap<String, Integer>
TABLE_STREAM_CONSUMPTION_COLUMN_TO_INDEX;
@@ -253,6 +257,13 @@ public class MetadataGenerator {
}
AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX =
authenticationIntegrationsBuilder.build();
+ ImmutableMap.Builder<String, Integer> roleMappingsBuilder = new
ImmutableMap.Builder();
+ List<Column> roleMappingsColList =
SchemaTable.TABLE_MAP.get("role_mappings").getFullSchema();
+ for (int i = 0; i < roleMappingsColList.size(); i++) {
+
roleMappingsBuilder.put(roleMappingsColList.get(i).getName().toLowerCase(), i);
+ }
+ ROLE_MAPPINGS_COLUMN_TO_INDEX = roleMappingsBuilder.build();
+
ImmutableMap.Builder<String, Integer> tableStreamsBuilder = new
ImmutableMap.Builder();
List<Column> streamsBuilderColList =
SchemaTable.TABLE_MAP.get("table_streams")
.getFullSchema();
@@ -386,6 +397,10 @@ public class MetadataGenerator {
result =
authenticationIntegrationsMetadataResult(schemaTableParams);
columnIndex = AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
break;
+ case ROLE_MAPPINGS:
+ result = roleMappingsMetadataResult(schemaTableParams);
+ columnIndex = ROLE_MAPPINGS_COLUMN_TO_INDEX;
+ break;
case TABLE_STREAMS:
result = streamMetadataResult(schemaTableParams);
columnIndex = TABLE_STREAMS_COLUMN_TO_INDEX;
@@ -777,6 +792,68 @@ public class MetadataGenerator {
return result;
}
+ private static TFetchSchemaTableDataResult
roleMappingsMetadataResult(TSchemaTableRequestParams params) {
+ if (!params.isSetCurrentUserIdent()) {
+ return errorResult("current user ident is not set.");
+ }
+ UserIdentity currentUserIdentity =
UserIdentity.fromThrift(params.getCurrentUserIdent());
+ TFetchSchemaTableDataResult result = new TFetchSchemaTableDataResult();
+ List<TRow> dataBatch = Lists.newArrayList();
+ result.setDataBatch(dataBatch);
+ result.setStatus(new TStatus(TStatusCode.OK));
+ if
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(currentUserIdentity,
PrivPredicate.ADMIN)) {
+ return
errorResult(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR.formatErrorMsg("ADMIN"));
+ }
+
+ List<Expression> conjuncts = Collections.EMPTY_LIST;
+ if (params.isSetFrontendConjuncts()) {
+ conjuncts =
FrontendConjunctsUtils.convertToExpression(params.getFrontendConjuncts());
+ }
+ List<Expression> nameConjuncts =
FrontendConjunctsUtils.filterBySlotName(conjuncts, "NAME");
+ List<Expression> integrationNameConjuncts =
+ FrontendConjunctsUtils.filterBySlotName(conjuncts,
"INTEGRATION_NAME");
+
+ for (RoleMappingMeta meta :
Env.getCurrentEnv().getRoleMappingMgr().getRoleMappings().values()) {
+ if (FrontendConjunctsUtils.isFiltered(nameConjuncts, "NAME",
meta.getName())
+ || FrontendConjunctsUtils.isFiltered(
+ integrationNameConjuncts, "INTEGRATION_NAME",
meta.getIntegrationName())) {
+ continue;
+ }
+ TRow row = new TRow();
+ row.addToColumnValue(new TCell().setStringVal(meta.getName()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getIntegrationName()));
+ row.addToColumnValue(new
TCell().setStringVal(formatRoleMappingRules(meta.getRules())));
+ if (meta.getComment() == null) {
+ row.addToColumnValue(new TCell());
+ } else {
+ row.addToColumnValue(new
TCell().setStringVal(meta.getComment()));
+ }
+ row.addToColumnValue(new
TCell().setStringVal(meta.getCreateUser()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getCreateTimeString()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getAlterUser()));
+ row.addToColumnValue(new
TCell().setStringVal(meta.getModifyTimeString()));
+ dataBatch.add(row);
+ }
+ return result;
+ }
+
+ private static String
formatRoleMappingRules(List<RoleMappingMeta.RuleMeta> rules) {
+ List<String> serializedRules =
Lists.newArrayListWithCapacity(rules.size());
+ for (RoleMappingMeta.RuleMeta rule : rules) {
+ serializedRules.add(formatRoleMappingRule(rule));
+ }
+ return Joiner.on("; ").join(serializedRules);
+ }
+
+ private static String formatRoleMappingRule(RoleMappingMeta.RuleMeta rule)
{
+ return "RULE (USING CEL '" +
escapeRoleMappingCondition(rule.getCondition()) + "' GRANT ROLE "
+ + Joiner.on(", ").join(rule.getGrantedRoles()) + ")";
+ }
+
+ private static String escapeRoleMappingCondition(String condition) {
+ return condition.replace("'", "''");
+ }
+
private static String maskAuthenticationProperties(Map<String, String>
properties) {
Map<String, String> maskedProperties = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : properties.entrySet()) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
index 9970d50a37a..8f0f875f25f 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
@@ -102,5 +102,18 @@ public class SchemaTableTest {
Assertions.assertEquals("CREATE_TIME",
authenticationIntegrations.getFullSchema().get(5).getName());
Assertions.assertEquals("ALTER_USER",
authenticationIntegrations.getFullSchema().get(6).getName());
Assertions.assertEquals("MODIFY_TIME",
authenticationIntegrations.getFullSchema().get(7).getName());
+
+ SchemaTable roleMappings = (SchemaTable)
SchemaTable.TABLE_MAP.get("role_mappings");
+ Assertions.assertFalse(roleMappings.shouldFetchAllFe());
+ Assertions.assertFalse(roleMappings.shouldAddAgg());
+ Assertions.assertEquals(8, roleMappings.getFullSchema().size());
+ Assertions.assertEquals("NAME",
roleMappings.getFullSchema().get(0).getName());
+ Assertions.assertEquals("INTEGRATION_NAME",
roleMappings.getFullSchema().get(1).getName());
+ Assertions.assertEquals("RULES",
roleMappings.getFullSchema().get(2).getName());
+ Assertions.assertEquals("COMMENT",
roleMappings.getFullSchema().get(3).getName());
+ Assertions.assertEquals("CREATE_USER",
roleMappings.getFullSchema().get(4).getName());
+ Assertions.assertEquals("CREATE_TIME",
roleMappings.getFullSchema().get(5).getName());
+ Assertions.assertEquals("ALTER_USER",
roleMappings.getFullSchema().get(6).getName());
+ Assertions.assertEquals("MODIFY_TIME",
roleMappings.getFullSchema().get(7).getName());
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index 00903892b23..90bcb41bed1 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -17,14 +17,17 @@
package org.apache.doris.service;
+import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
import org.apache.doris.common.Config;
+import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.util.DatasourcePrintableMap;
import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.commands.Command;
import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -105,6 +108,17 @@ public class FrontendServiceImplTest {
}
}
+ private static void executeCommand(String sql) throws Exception {
+ NereidsParser nereidsParser = new NereidsParser();
+ LogicalPlan parsed = nereidsParser.parseSingle(sql);
+ StmtExecutor stmtExecutor = new StmtExecutor(connectContext, sql);
+ if (parsed instanceof Command) {
+ ((Command) parsed).run(connectContext, stmtExecutor);
+ return;
+ }
+ throw new IllegalArgumentException("Unsupported command in test: " +
sql);
+ }
+
@Test
public void testCreatePartitionRange() throws Exception {
@@ -303,4 +317,114 @@ public class FrontendServiceImplTest {
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
}
}
+
+ @Test
+ public void testFetchRoleMappingsSchemaTableData() throws Exception {
+ String mappingName = "test_role_mapping_system_table";
+ String integrationName = "test_role_mapping_system_table_ldap";
+ String readerRole = "test_role_mapping_reader";
+ String financeReaderRole = "test_role_mapping_fin_reader";
+ String financeWriterRole = "test_role_mapping_fin_writer";
+ String expectedRules = "RULE (USING CEL 'has_group(\"analyst\")' GRANT
ROLE " + readerRole + "); "
+ + "RULE (USING CEL 'attr(\"department\") == \"finance\"' GRANT
ROLE "
+ + financeReaderRole + ", " + financeWriterRole + ")";
+
+ executeCommand("DROP ROLE MAPPING IF EXISTS " + mappingName);
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+ executeCommand("DROP ROLE IF EXISTS " + financeWriterRole);
+ executeCommand("DROP ROLE IF EXISTS " + financeReaderRole);
+ executeCommand("DROP ROLE IF EXISTS " + readerRole);
+
+ try {
+ executeCommand("CREATE ROLE " + readerRole);
+ executeCommand("CREATE ROLE " + financeReaderRole);
+ executeCommand("CREATE ROLE " + financeWriterRole);
+ executeCommand("CREATE AUTHENTICATION INTEGRATION " +
integrationName
+ + " PROPERTIES ('type'='ldap',
'ldap.server'='ldap://127.0.0.1:389') "
+ + "COMMENT 'role mapping auth'");
+ executeCommand("CREATE ROLE MAPPING " + mappingName
+ + " ON AUTHENTICATION INTEGRATION " + integrationName
+ + " RULE (USING CEL 'has_group(\"analyst\")' GRANT ROLE "
+ readerRole + ")"
+ + ", RULE (USING CEL 'attr(\"department\") == \"finance\"'
GRANT ROLE "
+ + financeReaderRole + ", " + financeWriterRole + ")"
+ + " COMMENT 'role mapping comment'");
+
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TFetchSchemaTableDataRequest request = new
TFetchSchemaTableDataRequest();
+ request.setSchemaTableName(TSchemaTableName.ROLE_MAPPINGS);
+ TSchemaTableRequestParams params = new TSchemaTableRequestParams();
+
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+ request.setSchemaTableParams(params);
+
+ TFetchSchemaTableDataResult result =
impl.fetchSchemaTableData(request);
+ Assert.assertEquals(TStatusCode.OK,
result.getStatus().getStatusCode());
+
+ List<String> rowValues = result.getDataBatch().stream()
+ .filter(row ->
mappingName.equals(row.getColumnValue().get(0).getStringVal()))
+ .map(row -> row.getColumnValue().stream()
+ .map(cell -> cell.isSetStringVal() ?
cell.getStringVal() : null)
+ .collect(Collectors.toList()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("role mapping row
not found"));
+
+ Assert.assertEquals(mappingName, rowValues.get(0));
+ Assert.assertEquals(integrationName, rowValues.get(1));
+ Assert.assertEquals(expectedRules, rowValues.get(2));
+ Assert.assertEquals("role mapping comment", rowValues.get(3));
+ Assert.assertEquals(connectContext.getQualifiedUser(),
rowValues.get(4));
+ Assert.assertNotNull(rowValues.get(5));
+ Assert.assertFalse(rowValues.get(5).isEmpty());
+ Assert.assertEquals(connectContext.getQualifiedUser(),
rowValues.get(6));
+ Assert.assertEquals(rowValues.get(5), rowValues.get(7));
+ } finally {
+ executeCommand("DROP ROLE MAPPING IF EXISTS " + mappingName);
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+ executeCommand("DROP ROLE IF EXISTS " + financeWriterRole);
+ executeCommand("DROP ROLE IF EXISTS " + financeReaderRole);
+ executeCommand("DROP ROLE IF EXISTS " + readerRole);
+ }
+ }
+
+ @Test
+ public void testFetchRoleMappingsSchemaTableDataWithoutAdmin() throws
Exception {
+ String mappingName = "test_role_mapping_non_admin_system_table";
+ String integrationName =
"test_role_mapping_non_admin_system_table_ldap";
+ String readerRole = "test_role_mapping_non_admin_reader";
+ String normalUser = "test_role_mapping_non_admin_user";
+
+ executeCommand("DROP ROLE MAPPING IF EXISTS " + mappingName);
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+ executeCommand("DROP USER IF EXISTS " + normalUser);
+ executeCommand("DROP ROLE IF EXISTS " + readerRole);
+
+ try {
+ executeCommand("CREATE ROLE " + readerRole);
+ executeCommand("CREATE USER " + normalUser);
+ executeCommand("CREATE AUTHENTICATION INTEGRATION " +
integrationName
+ + " PROPERTIES ('type'='ldap',
'ldap.server'='ldap://127.0.0.1:389') "
+ + "COMMENT 'role mapping auth'");
+ executeCommand("CREATE ROLE MAPPING " + mappingName
+ + " ON AUTHENTICATION INTEGRATION " + integrationName
+ + " RULE (USING CEL 'has_group(\"analyst\")' GRANT ROLE "
+ readerRole + ")"
+ + " COMMENT 'role mapping comment'");
+
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TFetchSchemaTableDataRequest request = new
TFetchSchemaTableDataRequest();
+ request.setSchemaTableName(TSchemaTableName.ROLE_MAPPINGS);
+ TSchemaTableRequestParams params = new TSchemaTableRequestParams();
+
params.setCurrentUserIdent(UserIdentity.createAnalyzedUserIdentWithIp(normalUser,
"%").toThrift());
+ request.setSchemaTableParams(params);
+
+ TFetchSchemaTableDataResult result =
impl.fetchSchemaTableData(request);
+ Assert.assertEquals(TStatusCode.INTERNAL_ERROR,
result.getStatus().getStatusCode());
+ Assert.assertEquals(1, result.getStatus().getErrorMsgsSize());
+
Assert.assertEquals(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR.formatErrorMsg("ADMIN"),
+ result.getStatus().getErrorMsgs().get(0));
+ } finally {
+ executeCommand("DROP ROLE MAPPING IF EXISTS " + mappingName);
+
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
true);
+ executeCommand("DROP USER IF EXISTS " + normalUser);
+ executeCommand("DROP ROLE IF EXISTS " + readerRole);
+ }
+ }
}
diff --git a/gensrc/thrift/Descriptors.thrift b/gensrc/thrift/Descriptors.thrift
index 9026c21086c..432da0d49c3 100644
--- a/gensrc/thrift/Descriptors.thrift
+++ b/gensrc/thrift/Descriptors.thrift
@@ -218,6 +218,7 @@ enum TSchemaTableType {
SCH_TABLE_STREAMS = 68;
SCH_TABLE_STREAM_CONSUMPTION = 69;
SCH_BE_COMPACTION_TASKS = 70;
+ SCH_ROLE_MAPPINGS = 71;
}
enum THdfsCompression {
diff --git a/gensrc/thrift/FrontendService.thrift
b/gensrc/thrift/FrontendService.thrift
index 34f56fe2510..1d8f6e6bbd9 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -874,6 +874,7 @@ enum TSchemaTableName {
AUTHENTICATION_INTEGRATIONS = 14,
TABLE_STREAMS = 15,
TABLE_STREAM_CONSUMPTION = 16,
+ ROLE_MAPPINGS = 17,
}
struct TMetadataTableRequestParams {
diff --git
a/regression-test/suites/auth_p0/test_role_mapping_system_table.groovy
b/regression-test/suites/auth_p0/test_role_mapping_system_table.groovy
new file mode 100644
index 00000000000..284b533a352
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_role_mapping_system_table.groovy
@@ -0,0 +1,123 @@
+// 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_role_mapping_system_table", "p0,auth") {
+ String suiteName = "test_role_mapping_system_table"
+ String mappingName = "test_role_mapping_system_table_mapping"
+ String integrationName = "test_role_mapping_system_table_ldap"
+ String readerRole = "test_role_mapping_reader"
+ String financeReaderRole = "test_role_mapping_fin_reader"
+ String financeWriterRole = "test_role_mapping_fin_writer"
+ String user = "${suiteName}_user"
+ String pwd = 'C123_567p'
+ def jdbcUrlTokens = context.config.jdbcUrl.split('/')
+ String jdbcUrlWithoutSchema = jdbcUrlTokens[0] + "//" + jdbcUrlTokens[2] +
"/?"
+ String expectedRules = (
+ """RULE (USING CEL 'has_group("analyst")' GRANT ROLE
${readerRole}); """
+ + """RULE (USING CEL 'attr("department") == "finance"' """
+ + """GRANT ROLE ${financeReaderRole}, ${financeWriterRole})"""
+ )
+
+ try_sql("DROP ROLE MAPPING IF EXISTS ${mappingName}")
+ try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+ try_sql("DROP USER ${user}")
+ try_sql("DROP ROLE IF EXISTS ${financeWriterRole}")
+ try_sql("DROP ROLE IF EXISTS ${financeReaderRole}")
+ try_sql("DROP ROLE IF EXISTS ${readerRole}")
+
+ try {
+ sql """CREATE ROLE ${readerRole}"""
+ sql """CREATE ROLE ${financeReaderRole}"""
+ sql """CREATE ROLE ${financeWriterRole}"""
+
+ sql """
+ CREATE AUTHENTICATION INTEGRATION ${integrationName}
+ PROPERTIES (
+ 'type'='ldap',
+ 'ldap.server'='ldap://127.0.0.1:389'
+ )
+ COMMENT 'role mapping auth'
+ """
+
+ sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
+
+ if (isCloudMode()) {
+ def clusters = sql " SHOW CLUSTERS; "
+ assertTrue(!clusters.isEmpty())
+ def validCluster = clusters[0][0]
+ sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""
+ }
+
+ sql """GRANT SELECT_PRIV ON internal.information_schema.* TO ${user}"""
+
+ sql """
+ CREATE ROLE MAPPING ${mappingName}
+ ON AUTHENTICATION INTEGRATION ${integrationName}
+ RULE ( USING CEL 'has_group("analyst")' GRANT ROLE ${readerRole} ),
+ RULE (
+ USING CEL 'attr("department") == "finance"'
+ GRANT ROLE ${financeReaderRole}, ${financeWriterRole}
+ )
+ COMMENT 'role mapping comment'
+ """
+
+ def result = sql """
+ SELECT
+ NAME,
+ INTEGRATION_NAME,
+ RULES,
+ COMMENT,
+ CREATE_USER,
+ CREATE_TIME,
+ ALTER_USER,
+ MODIFY_TIME
+ FROM information_schema.role_mappings
+ WHERE NAME = '${mappingName}'
+ ORDER BY NAME
+ """
+
+ assertEquals(1, result.size())
+ assertEquals(8, result[0].size())
+ assertEquals(mappingName, result[0][0])
+ assertEquals(integrationName, result[0][1])
+ assertEquals(expectedRules, result[0][2])
+ assertEquals("role mapping comment", result[0][3])
+ assertTrue(result[0][4] != null && result[0][4].length() > 0)
+ assertTrue(result[0][5] != null && result[0][5].length() > 0)
+ assertTrue(result[0][6] != null && result[0][6].length() > 0)
+ assertEquals(result[0][5], result[0][7])
+
+ connect(user, "${pwd}", jdbcUrlWithoutSchema) {
+ test {
+ sql """
+ SELECT NAME
+ FROM information_schema.role_mappings
+ WHERE NAME = '${mappingName}'
+ ORDER BY NAME
+ """
+ exception "Access denied; you need (at least one of) the
(ADMIN) privilege(s) for this operation"
+ }
+ }
+ } finally {
+ try_sql("DROP ROLE MAPPING IF EXISTS ${mappingName}")
+ try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+ try_sql("DROP USER ${user}")
+ try_sql("DROP ROLE IF EXISTS ${financeWriterRole}")
+ try_sql("DROP ROLE IF EXISTS ${financeReaderRole}")
+ try_sql("DROP ROLE IF EXISTS ${readerRole}")
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]