Add WITH IMPLICIT, fix error with implicit grants

This commit is contained in:
pufit 2024-10-02 23:20:33 -04:00
parent 229ead5f91
commit 2a107e3429
14 changed files with 282 additions and 207 deletions

View File

@ -351,7 +351,7 @@ Shows privileges for a user.
**Syntax**
``` sql
SHOW GRANTS [FOR user1 [, user2 ...]]
SHOW GRANTS [FOR user1 [, user2 ...]] [WITH IMPLICIT]
```
If user is not specified, the query returns privileges for the current user.

View File

@ -234,7 +234,7 @@ SHOW DICTIONARIES FROM db LIKE '%reg%' LIMIT 2
### Синтаксис {#show-grants-syntax}
``` sql
SHOW GRANTS [FOR user]
SHOW GRANTS [FOR user1 [, user2 ...]] [WITH IMPLICIT]
```
Если пользователь не задан, запрос возвращает привилегии текущего пользователя.

View File

@ -110,7 +110,7 @@ SHOW DICTIONARIES FROM db LIKE '%reg%' LIMIT 2
### 语法 {#show-grants-syntax}
``` sql
SHOW GRANTS [FOR user]
SHOW GRANTS [FOR user1 [, user2 ...]] [WITH IMPLICIT]
```
如果未指定用户,输出当前用户的权限

View File

@ -1155,9 +1155,6 @@ private:
calculateMinMaxFlags();
if (!isLeaf())
return;
auto new_flags = function(flags, min_flags_with_children, max_flags_with_children, level, grant_option);
if (new_flags != flags)

View File

@ -64,196 +64,6 @@ namespace
}
AccessRights addImplicitAccessRights(const AccessRights & access, const AccessControl & access_control)
{
AccessFlags max_flags;
auto modifier = [&](const AccessFlags & flags,
const AccessFlags & min_flags_with_children,
const AccessFlags & max_flags_with_children,
const size_t level,
bool /* grant_option */) -> AccessFlags
{
AccessFlags res = flags;
/// CREATE_TABLE => CREATE_VIEW, DROP_TABLE => DROP_VIEW, ALTER_TABLE => ALTER_VIEW
static const AccessFlags create_table = AccessType::CREATE_TABLE;
static const AccessFlags create_view = AccessType::CREATE_VIEW;
static const AccessFlags drop_table = AccessType::DROP_TABLE;
static const AccessFlags drop_view = AccessType::DROP_VIEW;
static const AccessFlags alter_table = AccessType::ALTER_TABLE;
static const AccessFlags alter_view = AccessType::ALTER_VIEW;
if (res & create_table)
res |= create_view;
if (res & drop_table)
res |= drop_view;
if (res & alter_table)
res |= alter_view;
/// CREATE TABLE (on any database/table) => CREATE_TEMPORARY_TABLE (global)
static const AccessFlags create_temporary_table = AccessType::CREATE_TEMPORARY_TABLE;
if ((level == 0) && (max_flags_with_children & create_table))
res |= create_temporary_table;
/// CREATE TABLE (on any database/table) => CREATE_ARBITRARY_TEMPORARY_TABLE (global)
static const AccessFlags create_arbitrary_temporary_table = AccessType::CREATE_ARBITRARY_TEMPORARY_TABLE;
if ((level == 0) && (max_flags_with_children & create_table))
res |= create_arbitrary_temporary_table;
/// ALTER_TTL => ALTER_MATERIALIZE_TTL
static const AccessFlags alter_ttl = AccessType::ALTER_TTL;
static const AccessFlags alter_materialize_ttl = AccessType::ALTER_MATERIALIZE_TTL;
if (res & alter_ttl)
res |= alter_materialize_ttl;
/// RELOAD_DICTIONARY (global) => RELOAD_EMBEDDED_DICTIONARIES (global)
static const AccessFlags reload_dictionary = AccessType::SYSTEM_RELOAD_DICTIONARY;
static const AccessFlags reload_embedded_dictionaries = AccessType::SYSTEM_RELOAD_EMBEDDED_DICTIONARIES;
if ((level == 0) && (min_flags_with_children & reload_dictionary))
res |= reload_embedded_dictionaries;
/// any column flag => SHOW_COLUMNS => SHOW_TABLES => SHOW_DATABASES
/// any table flag => SHOW_TABLES => SHOW_DATABASES
/// any dictionary flag => SHOW_DICTIONARIES => SHOW_DATABASES
/// any database flag => SHOW_DATABASES
static const AccessFlags show_columns = AccessType::SHOW_COLUMNS;
static const AccessFlags show_tables = AccessType::SHOW_TABLES;
static const AccessFlags show_dictionaries = AccessType::SHOW_DICTIONARIES;
static const AccessFlags show_tables_or_dictionaries = show_tables | show_dictionaries;
static const AccessFlags show_databases = AccessType::SHOW_DATABASES;
if (res & AccessFlags::allColumnFlags())
res |= show_columns;
if ((res & AccessFlags::allTableFlags())
|| (level <= 2 && (res & show_columns))
|| (level == 2 && (max_flags_with_children & show_columns)))
{
res |= show_tables;
}
if (res & AccessFlags::allDictionaryFlags())
res |= show_dictionaries;
if ((res & AccessFlags::allDatabaseFlags())
|| (level <= 1 && (res & show_tables_or_dictionaries))
|| (level == 1 && (max_flags_with_children & show_tables_or_dictionaries)))
{
res |= show_databases;
}
max_flags |= res;
return res;
};
AccessRights res = access;
res.modifyFlags(modifier);
/// If "select_from_system_db_requires_grant" is enabled we provide implicit grants only for a few tables in the system database.
if (access_control.doesSelectFromSystemDatabaseRequireGrant())
{
const char * always_accessible_tables[] = {
/// Constant tables
"one",
/// "numbers", "numbers_mt", "zeros", "zeros_mt" were excluded because they can generate lots of values and
/// that can decrease performance in some cases.
"contributors",
"licenses",
"time_zones",
"collations",
"formats",
"privileges",
"data_type_families",
"database_engines",
"table_engines",
"table_functions",
"aggregate_function_combinators",
"functions", /// Can contain user-defined functions
/// The following tables hide some rows if the current user doesn't have corresponding SHOW privileges.
"databases",
"tables",
"columns",
/// Specific to the current session
"settings",
"current_roles",
"enabled_roles",
"quota_usage"
};
for (const auto * table_name : always_accessible_tables)
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, table_name);
if (max_flags.contains(AccessType::SHOW_USERS))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "users");
if (max_flags.contains(AccessType::SHOW_ROLES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "roles");
if (max_flags.contains(AccessType::SHOW_ROW_POLICIES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "row_policies");
if (max_flags.contains(AccessType::SHOW_SETTINGS_PROFILES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "settings_profiles");
if (max_flags.contains(AccessType::SHOW_QUOTAS))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "quotas");
}
else
{
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
}
/// If "select_from_information_schema_requires_grant" is enabled we don't provide implicit grants for the information_schema database.
if (!access_control.doesSelectFromInformationSchemaRequireGrant())
{
res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA);
res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA_UPPERCASE);
}
/// There is overlap between AccessType sources and table engines, so the following code avoids user granting twice.
/// Sync SOURCE and TABLE_ENGINE, so only need to check TABLE_ENGINE later.
if (access_control.doesTableEnginesRequireGrant())
{
for (const auto & source_and_table_engine : source_and_table_engines)
{
const auto & source = std::get<0>(source_and_table_engine);
if (res.isGranted(source))
{
const auto & table_engine = std::get<1>(source_and_table_engine);
res.grant(AccessType::TABLE_ENGINE, table_engine);
}
}
}
else
{
/// Add TABLE_ENGINE on * and then remove TABLE_ENGINE on particular engines.
res.grant(AccessType::TABLE_ENGINE);
for (const auto & source_and_table_engine : source_and_table_engines)
{
const auto & source = std::get<0>(source_and_table_engine);
if (!res.isGranted(source))
{
const auto & table_engine = std::get<1>(source_and_table_engine);
res.revoke(AccessType::TABLE_ENGINE, table_engine);
}
}
}
return res;
}
std::array<UUID, 1> to_array(const UUID & id)
{
std::array<UUID, 1> ids;
@ -274,6 +84,196 @@ namespace
}
AccessRights ContextAccess::addImplicitAccessRights(const AccessRights & access, const AccessControl & access_control)
{
AccessFlags max_flags;
auto modifier = [&](const AccessFlags & flags,
const AccessFlags & min_flags_with_children,
const AccessFlags & max_flags_with_children,
const size_t level,
bool /* grant_option */) -> AccessFlags
{
AccessFlags res = flags;
/// CREATE_TABLE => CREATE_VIEW, DROP_TABLE => DROP_VIEW, ALTER_TABLE => ALTER_VIEW
static const AccessFlags create_table = AccessType::CREATE_TABLE;
static const AccessFlags create_view = AccessType::CREATE_VIEW;
static const AccessFlags drop_table = AccessType::DROP_TABLE;
static const AccessFlags drop_view = AccessType::DROP_VIEW;
static const AccessFlags alter_table = AccessType::ALTER_TABLE;
static const AccessFlags alter_view = AccessType::ALTER_VIEW;
if (res & create_table)
res |= create_view;
if (res & drop_table)
res |= drop_view;
if (res & alter_table)
res |= alter_view;
/// CREATE TABLE (on any database/table) => CREATE_TEMPORARY_TABLE (global)
static const AccessFlags create_temporary_table = AccessType::CREATE_TEMPORARY_TABLE;
if ((level == 0) && (max_flags_with_children & create_table))
res |= create_temporary_table;
/// CREATE TABLE (on any database/table) => CREATE_ARBITRARY_TEMPORARY_TABLE (global)
static const AccessFlags create_arbitrary_temporary_table = AccessType::CREATE_ARBITRARY_TEMPORARY_TABLE;
if ((level == 0) && (max_flags_with_children & create_table))
res |= create_arbitrary_temporary_table;
/// ALTER_TTL => ALTER_MATERIALIZE_TTL
static const AccessFlags alter_ttl = AccessType::ALTER_TTL;
static const AccessFlags alter_materialize_ttl = AccessType::ALTER_MATERIALIZE_TTL;
if (res & alter_ttl)
res |= alter_materialize_ttl;
/// RELOAD_DICTIONARY (global) => RELOAD_EMBEDDED_DICTIONARIES (global)
static const AccessFlags reload_dictionary = AccessType::SYSTEM_RELOAD_DICTIONARY;
static const AccessFlags reload_embedded_dictionaries = AccessType::SYSTEM_RELOAD_EMBEDDED_DICTIONARIES;
if ((level == 0) && (min_flags_with_children & reload_dictionary))
res |= reload_embedded_dictionaries;
/// any column flag => SHOW_COLUMNS => SHOW_TABLES => SHOW_DATABASES
/// any table flag => SHOW_TABLES => SHOW_DATABASES
/// any dictionary flag => SHOW_DICTIONARIES => SHOW_DATABASES
/// any database flag => SHOW_DATABASES
static const AccessFlags show_columns = AccessType::SHOW_COLUMNS;
static const AccessFlags show_tables = AccessType::SHOW_TABLES;
static const AccessFlags show_dictionaries = AccessType::SHOW_DICTIONARIES;
static const AccessFlags show_tables_or_dictionaries = show_tables | show_dictionaries;
static const AccessFlags show_databases = AccessType::SHOW_DATABASES;
if (res & AccessFlags::allColumnFlags())
res |= show_columns;
if ((res & AccessFlags::allTableFlags())
|| (level <= 2 && (res & show_columns))
|| (level == 2 && (max_flags_with_children & show_columns)))
{
res |= show_tables;
}
if (res & AccessFlags::allDictionaryFlags())
res |= show_dictionaries;
if ((res & AccessFlags::allDatabaseFlags())
|| (level <= 1 && (res & show_tables_or_dictionaries))
|| (level == 1 && (max_flags_with_children & show_tables_or_dictionaries)))
{
res |= show_databases;
}
max_flags |= res;
return res;
};
AccessRights res = access;
res.modifyFlags(modifier);
/// If "select_from_system_db_requires_grant" is enabled we provide implicit grants only for a few tables in the system database.
if (access_control.doesSelectFromSystemDatabaseRequireGrant())
{
const char * always_accessible_tables[] = {
/// Constant tables
"one",
/// "numbers", "numbers_mt", "zeros", "zeros_mt" were excluded because they can generate lots of values and
/// that can decrease performance in some cases.
"contributors",
"licenses",
"time_zones",
"collations",
"formats",
"privileges",
"data_type_families",
"database_engines",
"table_engines",
"table_functions",
"aggregate_function_combinators",
"functions", /// Can contain user-defined functions
/// The following tables hide some rows if the current user doesn't have corresponding SHOW privileges.
"databases",
"tables",
"columns",
/// Specific to the current session
"settings",
"current_roles",
"enabled_roles",
"quota_usage"
};
for (const auto * table_name : always_accessible_tables)
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, table_name);
if (max_flags.contains(AccessType::SHOW_USERS))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "users");
if (max_flags.contains(AccessType::SHOW_ROLES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "roles");
if (max_flags.contains(AccessType::SHOW_ROW_POLICIES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "row_policies");
if (max_flags.contains(AccessType::SHOW_SETTINGS_PROFILES))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "settings_profiles");
if (max_flags.contains(AccessType::SHOW_QUOTAS))
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "quotas");
}
else
{
res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
}
/// If "select_from_information_schema_requires_grant" is enabled we don't provide implicit grants for the information_schema database.
if (!access_control.doesSelectFromInformationSchemaRequireGrant())
{
res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA);
res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA_UPPERCASE);
}
/// There is overlap between AccessType sources and table engines, so the following code avoids user granting twice.
/// Sync SOURCE and TABLE_ENGINE, so only need to check TABLE_ENGINE later.
if (access_control.doesTableEnginesRequireGrant())
{
for (const auto & source_and_table_engine : source_and_table_engines)
{
const auto & source = std::get<0>(source_and_table_engine);
if (res.isGranted(source))
{
const auto & table_engine = std::get<1>(source_and_table_engine);
res.grant(AccessType::TABLE_ENGINE, table_engine);
}
}
}
else
{
/// Add TABLE_ENGINE on * and then remove TABLE_ENGINE on particular engines.
res.grant(AccessType::TABLE_ENGINE);
for (const auto & source_and_table_engine : source_and_table_engines)
{
const auto & source = std::get<0>(source_and_table_engine);
if (!res.isGranted(source))
{
const auto & table_engine = std::get<1>(source_and_table_engine);
res.revoke(AccessType::TABLE_ENGINE, table_engine);
}
}
}
return res;
}
std::shared_ptr<const ContextAccess> ContextAccess::fromContext(const ContextPtr & context)
{
return ContextAccessWrapper::fromContext(context)->getAccess();

View File

@ -133,6 +133,8 @@ public:
/// Checks if grantees are allowed for the current user, throws an exception if not.
void checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const;
static AccessRights addImplicitAccessRights(const AccessRights & access, const AccessControl & access_control);
ContextAccess(const AccessControl & access_control_, const Params & params_);
~ContextAccess();

View File

@ -32,7 +32,8 @@ namespace
ASTs getGrantQueriesImpl(
const T & grantee,
const AccessControl * access_control /* not used if attach_mode == true */,
bool attach_mode = false)
bool attach_mode = false,
bool with_implicit = false)
{
ASTs res;
@ -41,7 +42,13 @@ namespace
std::shared_ptr<ASTGrantQuery> current_query = nullptr;
for (const auto & element : grantee.access.getElements())
AccessRightsElements elements;
if (with_implicit)
elements = ContextAccess::addImplicitAccessRights(grantee.access, *access_control).getElements();
else
elements = grantee.access.getElements();
for (const auto & element : elements)
{
if (element.empty())
continue;
@ -89,12 +96,13 @@ namespace
ASTs getGrantQueriesImpl(
const IAccessEntity & entity,
const AccessControl * access_control /* not used if attach_mode == true */,
bool attach_mode = false)
bool attach_mode = false,
bool with_implicit = false)
{
if (const User * user = typeid_cast<const User *>(&entity))
return getGrantQueriesImpl(*user, access_control, attach_mode);
return getGrantQueriesImpl(*user, access_control, attach_mode, with_implicit);
if (const Role * role = typeid_cast<const Role *>(&entity))
return getGrantQueriesImpl(*role, access_control, attach_mode);
return getGrantQueriesImpl(*role, access_control, attach_mode, with_implicit);
throw Exception(ErrorCodes::LOGICAL_ERROR, "{} is expected to be user or role", entity.formatTypeWithName());
}
@ -180,23 +188,24 @@ ASTs InterpreterShowGrantsQuery::getGrantQueries() const
auto entities = getEntities();
const auto & access_control = getContext()->getAccessControl();
const auto & show_query = query_ptr->as<const ASTShowGrantsQuery &>();
ASTs grant_queries;
for (const auto & entity : entities)
boost::range::push_back(grant_queries, getGrantQueries(*entity, access_control));
boost::range::push_back(grant_queries, getGrantQueries(*entity, access_control, show_query.with_implicit));
return grant_queries;
}
ASTs InterpreterShowGrantsQuery::getGrantQueries(const IAccessEntity & user_or_role, const AccessControl & access_control)
ASTs InterpreterShowGrantsQuery::getGrantQueries(const IAccessEntity & user_or_role, const AccessControl & access_control, bool with_implicit)
{
return getGrantQueriesImpl(user_or_role, &access_control, false);
return getGrantQueriesImpl(user_or_role, &access_control, false, with_implicit);
}
ASTs InterpreterShowGrantsQuery::getAttachGrantQueries(const IAccessEntity & user_or_role)
{
return getGrantQueriesImpl(user_or_role, nullptr, true);
return getGrantQueriesImpl(user_or_role, nullptr, true, false);
}
void registerInterpreterShowGrantsQuery(InterpreterFactory & factory)

View File

@ -20,7 +20,7 @@ public:
BlockIO execute() override;
static ASTs getGrantQueries(const IAccessEntity & user_or_role, const AccessControl & access_control);
static ASTs getGrantQueries(const IAccessEntity & user_or_role, const AccessControl & access_control, bool with_implicit = false);
static ASTs getAttachGrantQueries(const IAccessEntity & user_or_role);
bool ignoreQuota() const override { return true; }

View File

@ -38,5 +38,11 @@ void ASTShowGrantsQuery::formatQueryImpl(const FormatSettings & settings, Format
<< (settings.hilite ? hilite_none : "");
for_roles->format(settings);
}
if (with_implicit)
{
settings.ostr << (settings.hilite ? hilite_keyword : "") << " WITH IMPLICIT"
<< (settings.hilite ? hilite_none : "");
}
}
}

View File

@ -7,12 +7,13 @@ namespace DB
{
class ASTRolesOrUsersSet;
/** SHOW GRANTS [FOR user_name]
/** SHOW GRANTS [FOR user1 [, user2 ...]] [WITH IMPLICIT]
*/
class ASTShowGrantsQuery : public ASTQueryWithOutput
{
public:
std::shared_ptr<ASTRolesOrUsersSet> for_roles;
bool with_implicit = false;
String getID(char) const override;
ASTPtr clone() const override;

View File

@ -31,8 +31,13 @@ bool ParserShowGrantsQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
for_roles->current_user = true;
}
bool with_implicit = false;
if (ParserKeyword{Keyword::WITH_IMPLICIT}.ignore(pos, expected))
with_implicit = true;
auto query = std::make_shared<ASTShowGrantsQuery>();
query->for_roles = std::move(for_roles);
query->with_implicit = with_implicit;
node = query;
return true;

View File

@ -530,6 +530,7 @@ namespace DB
MR_MACROS(WITH_NAME, "WITH NAME") \
MR_MACROS(WITH_REPLACE_OPTION, "WITH REPLACE OPTION") \
MR_MACROS(WITH_TIES, "WITH TIES") \
MR_MACROS(WITH_IMPLICIT, "WITH IMPLICIT") \
MR_MACROS(WITH, "WITH") \
MR_MACROS(RECURSIVE, "RECURSIVE") \
MR_MACROS(WK, "WK") \

View File

@ -0,0 +1,29 @@
Empty grants
GRANT SOURCES ON *.*
GRANT TABLE ENGINE ON *
GRANT SELECT ON system.aggregate_function_combinators
GRANT SELECT ON system.collations
GRANT SELECT ON system.columns
GRANT SELECT ON system.contributors
GRANT SELECT ON system.current_roles
GRANT SELECT ON system.data_type_families
GRANT SELECT ON system.database_engines
GRANT SELECT ON system.databases
GRANT SELECT ON system.enabled_roles
GRANT SELECT ON system.formats
GRANT SELECT ON system.functions
GRANT SELECT ON system.licenses
GRANT SELECT ON system.one
GRANT SELECT ON system.privileges
GRANT SELECT ON system.quota_usage
GRANT SELECT ON system.settings
GRANT SELECT ON system.table_engines
GRANT SELECT ON system.table_functions
GRANT SELECT ON system.tables
GRANT SELECT ON system.time_zones
Revoke grants
GRANT SHOW DATABASES, SHOW TABLES, SHOW COLUMNS, SELECT, SOURCES ON *.*
GRANT TABLE ENGINE ON *
REVOKE SHOW TABLES, SHOW COLUMNS, SELECT ON test_03247.`table`
OK
0

View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
user="user03247_${CLICKHOUSE_DATABASE}_$RANDOM"
${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS $user;";
${CLICKHOUSE_CLIENT} --query "CREATE USER $user;";
${CLICKHOUSE_CLIENT} --query "GRANT SOURCES ON *.* TO $user;";
echo "Empty grants";
${CLICKHOUSE_CLIENT} --query "SHOW GRANTS FOR $user WITH IMPLICIT;" | sed 's/ TO.*//';
echo "Revoke grants";
${CLICKHOUSE_CLIENT} --query "GRANT SELECT ON *.* TO $user ;";
${CLICKHOUSE_CLIENT} --query "REVOKE SELECT ON test_03247.table FROM $user;";
${CLICKHOUSE_CLIENT} --query "SHOW GRANTS FOR $user WITH IMPLICIT;" | sed 's/ TO.*//' | sed 's/ FROM.*//';
(( $(${CLICKHOUSE_CLIENT} --user $user --query "EXISTS test_03247.table;" 2>&1 | grep -c "Not enough privileges") >= 1 )) && echo "OK" || echo "UNEXPECTED";
${CLICKHOUSE_CLIENT} --query "EXISTS test_03247.table2;" --user $user;
${CLICKHOUSE_CLIENT} --query "DROP USER IF EXISTS $user;";