Skip to content

Commit 7264ca3

Browse files
authored
Merge branch 'releases/25.8.12' into backports/25.8.12/89367
2 parents 235d29d + 1b78bf2 commit 7264ca3

31 files changed

+698
-23
lines changed

ci/jobs/scripts/check_style/aspell-ignore/en/aspell-dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,7 @@ atanh
14221422
atomicity
14231423
auth
14241424
authType
1425+
authenticatedUser
14251426
authenticator
14261427
authenticators
14271428
autocompletion

ci/jobs/scripts/check_style/various_checks.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ for test_case in "${tests_with_replicated_merge_tree[@]}"; do
7575
done
7676

7777
# Check for existence of __init__.py files
78-
for i in "${ROOT_PATH}"/tests/integration/test_*; do FILE="${i}/__init__.py"; [ ! -f "${FILE}" ] && echo "${FILE} should exist for every integration test"; done
78+
# for i in "${ROOT_PATH}"/tests/integration/test_*; do FILE="${i}/__init__.py"; [ ! -f "${FILE}" ] && echo "${FILE} should exist for every integration test"; done
7979

8080
# Check for executable bit on non-executable files
8181
find $ROOT_PATH/{src,base,programs,utils,tests,docs,cmake} '(' -name '*.cpp' -or -name '*.h' -or -name '*.sql' -or -name '*.j2' -or -name '*.xml' -or -name '*.reference' -or -name '*.txt' -or -name '*.md' ')' -and -executable | grep -P '.' && echo "These files should not be executable."
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
description: 'Documentation for EXECUTE AS Statement'
3+
sidebar_label: 'EXECUTE AS'
4+
sidebar_position: 53
5+
slug: /sql-reference/statements/execute_as
6+
title: 'EXECUTE AS Statement'
7+
doc_type: 'reference'
8+
---
9+
10+
# EXECUTE AS Statement
11+
12+
Allows to execute queries on behalf of a different user.
13+
14+
## Syntax {#syntax}
15+
16+
```sql
17+
EXECUTE AS target_user;
18+
EXECUTE AS target_user subquery;
19+
```
20+
21+
The first form (without `subquery`) sets that all the following queries in the current session will be executed on behalf of the specified `target_user`.
22+
23+
The second form (with `subquery`) executes only the specified `subquery` on behalf of the specified `target_user`.
24+
25+
In order to work both forms require server setting [allow_impersonate_user](/operations/server-configuration-parameters/settings#allow_impersonate_user)
26+
to be set to `1` and the `IMPERSONATE` privilege to be granted. For example, the following commands
27+
```sql
28+
GRANT IMPERSONATE ON user1 TO user2;
29+
GRANT IMPERSONATE ON * TO user3;
30+
```
31+
allow user `user2` to execute commands `EXECUTE AS user1 ...` and also allow user `user3` to execute commands as any user.
32+
33+
While impersonating another user function [currentUser()](/sql-reference/functions/other-functions#currentUser) returns the name of that other user,
34+
and function [authenticatedUser()](/sql-reference/functions/other-functions#authenticatedUser) returns the name of the user who has been actually authenticated.
35+
36+
## Examples {#examples}
37+
38+
```sql
39+
SELECT currentUser(), authenticatedUser(); -- outputs "default default"
40+
CREATE USER james;
41+
EXECUTE AS james SELECT currentUser(), authenticatedUser(); -- outputs "james default"
42+
```

src/Access/Common/AccessType.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ enum class AccessType : uint8_t
290290
M(SHOW_QUOTAS, "SHOW CREATE QUOTA", GLOBAL, SHOW_ACCESS) \
291291
M(SHOW_SETTINGS_PROFILES, "SHOW PROFILES, SHOW CREATE SETTINGS PROFILE, SHOW CREATE PROFILE", GLOBAL, SHOW_ACCESS) \
292292
M(SHOW_ACCESS, "", GROUP, ACCESS_MANAGEMENT) \
293+
M(IMPERSONATE, "EXECUTE AS", USER_NAME, ACCESS_MANAGEMENT) \
293294
M(ACCESS_MANAGEMENT, "", GROUP, ALL) \
294295
M(SHOW_NAMED_COLLECTIONS, "SHOW NAMED COLLECTIONS", NAMED_COLLECTION, NAMED_COLLECTION_ADMIN) \
295296
M(SHOW_NAMED_COLLECTIONS_SECRETS, "SHOW NAMED COLLECTIONS SECRETS", NAMED_COLLECTION, NAMED_COLLECTION_ADMIN) \

src/Core/ServerSettings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ The policy on how to perform a scheduling of CPU slots specified by `concurrent_
11391139
DECLARE(UInt64, threadpool_local_fs_reader_queue_size, 1000000, R"(The maximum number of jobs that can be scheduled on the thread pool for reading from local filesystem.)", 0) \
11401140
DECLARE(NonZeroUInt64, threadpool_remote_fs_reader_pool_size, 250, R"(Number of threads in the Thread pool used for reading from remote filesystem when `remote_filesystem_read_method = 'threadpool'`.)", 0) \
11411141
DECLARE(UInt64, threadpool_remote_fs_reader_queue_size, 1000000, R"(The maximum number of jobs that can be scheduled on the thread pool for reading from remote filesystem.)", 0) \
1142-
1142+
DECLARE(Bool, allow_impersonate_user, false, R"(Enable/disable the IMPERSONATE feature (EXECUTE AS target_user).)", 0) \
11431143

11441144
// clang-format on
11451145

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include <Functions/IFunction.h>
2+
#include <Functions/FunctionFactory.h>
3+
#include <Interpreters/Context.h>
4+
#include <DataTypes/DataTypeString.h>
5+
#include <Core/Field.h>
6+
7+
8+
namespace DB
9+
{
10+
namespace
11+
{
12+
13+
class FunctionAuthenticatedUser : public IFunction
14+
{
15+
const String user_name;
16+
17+
public:
18+
static constexpr auto name = "authenticatedUser";
19+
static FunctionPtr create(ContextPtr context)
20+
{
21+
return std::make_shared<FunctionAuthenticatedUser>(context->getClientInfo().authenticated_user);
22+
}
23+
24+
explicit FunctionAuthenticatedUser(const String & user_name_) : user_name{user_name_}
25+
{
26+
}
27+
28+
String getName() const override
29+
{
30+
return name;
31+
}
32+
size_t getNumberOfArguments() const override
33+
{
34+
return 0;
35+
}
36+
37+
DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override
38+
{
39+
return std::make_shared<DataTypeString>();
40+
}
41+
42+
bool isDeterministic() const override { return false; }
43+
44+
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
45+
46+
ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr &, size_t input_rows_count) const override
47+
{
48+
return DataTypeString().createColumnConst(input_rows_count, user_name);
49+
}
50+
};
51+
52+
}
53+
54+
REGISTER_FUNCTION(AuthenticatedUser)
55+
{
56+
factory.registerFunction<FunctionAuthenticatedUser>(FunctionDocumentation{
57+
.description=R"(
58+
If the session user has been switched using the EXECUTE AS command, this function returns the name of the original user that was used for authentication and creating the session.
59+
Alias: authUser()
60+
)",
61+
.syntax=R"(authenticatedUser())",
62+
.arguments={},
63+
.returned_value={R"(The name of the authenticated user.)", {"String"}},
64+
.examples{
65+
{"Usage example",
66+
R"(
67+
EXECUTE as u1;
68+
SELECT currentUser(), authenticatedUser();
69+
)",
70+
R"(
71+
┌─currentUser()─┬─authenticatedUser()─┐
72+
│ u1 │ default │
73+
└───────────────┴─────────────────────┘
74+
)"
75+
}},
76+
.introduced_in = {25, 11},
77+
.category = FunctionDocumentation::Category::Other
78+
});
79+
80+
factory.registerAlias("authUser", "authenticatedUser");
81+
}
82+
83+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <Interpreters/Access/InterpreterExecuteAsQuery.h>
2+
3+
#include <Access/AccessControl.h>
4+
#include <Access/User.h>
5+
#include <Core/Settings.h>
6+
#include <Core/ServerSettings.h>
7+
#include <Parsers/Access/ASTExecuteAsQuery.h>
8+
#include <Parsers/Access/ASTUserNameWithHost.h>
9+
#include <Interpreters/Context.h>
10+
#include <Interpreters/InterpreterFactory.h>
11+
#include <Interpreters/QueryFlags.h>
12+
#include <Interpreters/executeQuery.h>
13+
14+
15+
namespace DB
16+
{
17+
18+
namespace ErrorCodes
19+
{
20+
extern const int SUPPORT_IS_DISABLED;
21+
}
22+
23+
namespace ServerSetting
24+
{
25+
extern const ServerSettingsBool allow_impersonate_user;
26+
}
27+
28+
namespace
29+
{
30+
/// Creates another query context to execute a query as another user.
31+
ContextMutablePtr impersonateQueryContext(ContextPtr context, const String & target_user_name)
32+
{
33+
auto new_context = Context::createCopy(context->getGlobalContext());
34+
new_context->setClientInfo(context->getClientInfo());
35+
new_context->makeQueryContext();
36+
37+
const auto & database = context->getCurrentDatabase();
38+
if (!database.empty() && database != new_context->getCurrentDatabase())
39+
new_context->setCurrentDatabase(database);
40+
41+
new_context->setInsertionTable(context->getInsertionTable(), context->getInsertionTableColumnNames());
42+
new_context->setProgressCallback(context->getProgressCallback());
43+
new_context->setProcessListElement(context->getProcessListElement());
44+
45+
if (context->getCurrentTransaction())
46+
new_context->setCurrentTransaction(context->getCurrentTransaction());
47+
48+
if (context->getZooKeeperMetadataTransaction())
49+
new_context->initZooKeeperMetadataTransaction(context->getZooKeeperMetadataTransaction());
50+
51+
new_context->setUser(context->getAccessControl().getID<User>(target_user_name));
52+
53+
/// We need to update the client info to make currentUser() return `target_user_name`.
54+
new_context->setCurrentUserName(target_user_name);
55+
new_context->setInitialUserName(target_user_name);
56+
57+
auto changed_settings = context->getSettingsRef().changes();
58+
new_context->clampToSettingsConstraints(changed_settings, SettingSource::QUERY);
59+
new_context->applySettingsChanges(changed_settings);
60+
61+
return new_context;
62+
}
63+
64+
/// Changes the session context to execute all following queries in this session as another user.
65+
void impersonateSessionContext(ContextMutablePtr context, const String & target_user_name)
66+
{
67+
auto database = context->getCurrentDatabase();
68+
auto changed_settings = context->getSettingsRef().changes();
69+
70+
context->setUser(context->getAccessControl().getID<User>(target_user_name));
71+
72+
/// We need to update the client info to make currentUser() return `target_user_name`.
73+
context->setCurrentUserName(target_user_name);
74+
context->setInitialUserName(target_user_name);
75+
76+
context->clampToSettingsConstraints(changed_settings, SettingSource::QUERY);
77+
context->applySettingsChanges(changed_settings);
78+
79+
if (!database.empty() && database != context->getCurrentDatabase())
80+
context->setCurrentDatabase(database);
81+
}
82+
}
83+
84+
85+
BlockIO InterpreterExecuteAsQuery::execute()
86+
{
87+
if (!getContext()->getGlobalContext()->getServerSettings()[ServerSetting::allow_impersonate_user])
88+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IMPERSONATE feature is disabled, set allow_impersonate_user to 1 to enable");
89+
90+
const auto & query = query_ptr->as<const ASTExecuteAsQuery &>();
91+
String target_user_name = query.target_user->toString();
92+
getContext()->checkAccess(AccessType::IMPERSONATE, target_user_name);
93+
94+
if (query.subquery)
95+
{
96+
/// EXECUTE AS <user> <subquery>
97+
auto subquery_context = impersonateQueryContext(getContext(), target_user_name);
98+
return executeQuery(query.subquery->formatWithSecretsOneLine(), subquery_context, QueryFlags{ .internal = true }).second;
99+
}
100+
else
101+
{
102+
/// EXECUTE AS <user>
103+
impersonateSessionContext(getContext()->getSessionContext(), target_user_name);
104+
return {};
105+
}
106+
}
107+
108+
109+
void registerInterpreterExecuteAsQuery(InterpreterFactory & factory)
110+
{
111+
auto create_fn = [] (const InterpreterFactory::Arguments & args)
112+
{
113+
return std::make_unique<InterpreterExecuteAsQuery>(args.query, args.context);
114+
};
115+
factory.registerInterpreter("InterpreterExecuteAsQuery", create_fn);
116+
}
117+
118+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <Interpreters/IInterpreter.h>
4+
#include <Parsers/IAST_fwd.h>
5+
6+
7+
namespace DB
8+
{
9+
10+
class InterpreterExecuteAsQuery : public IInterpreter, WithMutableContext
11+
{
12+
public:
13+
InterpreterExecuteAsQuery(const ASTPtr & query_ptr_, ContextMutablePtr context_) : WithMutableContext(context_), query_ptr(query_ptr_) {}
14+
BlockIO execute() override;
15+
16+
private:
17+
ASTPtr query_ptr;
18+
};
19+
20+
}

src/Interpreters/ClientInfo.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class ClientInfo
6565
String current_query_id;
6666
std::shared_ptr<Poco::Net::SocketAddress> current_address;
6767

68+
/// For IMPERSONATEd session, stores the original authenticated user
69+
String authenticated_user;
70+
6871
/// When query_kind == INITIAL_QUERY, these values are equal to current.
6972
String initial_user;
7073
String initial_query_id;

src/Interpreters/DDLWorker.cpp

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,6 @@ bool DDLWorker::initializeMainThread()
11391139
auto zookeeper = getAndSetZooKeeper();
11401140
zookeeper->createAncestors(fs::path(queue_dir) / "");
11411141
initializeReplication();
1142-
markReplicasActive(true);
11431142
initialized = true;
11441143
return true;
11451144
}
@@ -1212,6 +1211,14 @@ void DDLWorker::runMainThread()
12121211
}
12131212

12141213
cleanup_event->set();
1214+
try
1215+
{
1216+
markReplicasActive(reinitialized);
1217+
}
1218+
catch (...)
1219+
{
1220+
tryLogCurrentException(log, "An error occurred when markReplicasActive: ");
1221+
}
12151222
scheduleTasks(reinitialized);
12161223
subsequent_errors_count = 0;
12171224

@@ -1290,20 +1297,23 @@ void DDLWorker::createReplicaDirs(const ZooKeeperPtr & zookeeper, const NameSet
12901297
zookeeper->createAncestors(fs::path(replicas_dir) / host_id / "");
12911298
}
12921299

1293-
void DDLWorker::markReplicasActive(bool /*reinitialized*/)
1300+
void DDLWorker::markReplicasActive(bool reinitialized)
12941301
{
12951302
auto zookeeper = getZooKeeper();
12961303

1297-
// Reset all active_node_holders
1298-
for (auto & it : active_node_holders)
1304+
if (reinitialized)
12991305
{
1300-
auto & active_node_holder = it.second.second;
1301-
if (active_node_holder)
1302-
active_node_holder->setAlreadyRemoved();
1303-
active_node_holder.reset();
1304-
}
1306+
// Reset all active_node_holders
1307+
for (auto & it : active_node_holders)
1308+
{
1309+
auto & active_node_holder = it.second.second;
1310+
if (active_node_holder)
1311+
active_node_holder->setAlreadyRemoved();
1312+
active_node_holder.reset();
1313+
}
13051314

1306-
active_node_holders.clear();
1315+
active_node_holders.clear();
1316+
}
13071317

13081318
for (auto it = active_node_holders.begin(); it != active_node_holders.end();)
13091319
{
@@ -1384,12 +1394,7 @@ void DDLWorker::markReplicasActive(bool /*reinitialized*/)
13841394
{
13851395
zookeeper->deleteEphemeralNodeIfContentMatches(active_path, active_id);
13861396
}
1387-
Coordination::Requests ops;
1388-
ops.emplace_back(zkutil::makeCreateRequest(active_path, active_id, zkutil::CreateMode::Ephemeral));
1389-
/// To bump node mtime
1390-
ops.emplace_back(zkutil::makeSetRequest(fs::path(replicas_dir) / host_id, "", -1));
1391-
zookeeper->multi(ops);
1392-
1397+
zookeeper->create(active_path, active_id, zkutil::CreateMode::Ephemeral);
13931398
auto active_node_holder_zookeeper = zookeeper;
13941399
auto active_node_holder = zkutil::EphemeralNodeHolder::existing(active_path, *active_node_holder_zookeeper);
13951400
active_node_holders[host_id] = {active_node_holder_zookeeper, active_node_holder};

0 commit comments

Comments
 (0)