Merge pull request #12002 from vitlibar/fix-partial-revokes

Fix partial revokes
This commit is contained in:
Vitaly Baranov 2020-07-01 17:15:01 +03:00 committed by GitHub
commit 811d124a82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1378 additions and 1072 deletions

View File

@ -40,27 +40,8 @@ class AccessControlManager::ContextAccessCache
public:
explicit ContextAccessCache(const AccessControlManager & manager_) : manager(manager_) {}
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const boost::container::flat_set<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
const ClientInfo & client_info)
std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params)
{
ContextAccess::Params params;
params.user_id = user_id;
params.current_roles = current_roles;
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
params.allow_ddl = settings.allow_ddl;
params.allow_introspection = settings.allow_introspection_functions;
params.interface = client_info.interface;
params.http_method = client_info.http_method;
params.address = client_info.current_address.host();
params.quota_key = client_info.quota_key;
std::lock_guard lock{mutex};
auto x = cache.get(params);
if (x)
@ -119,7 +100,25 @@ std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(
const String & current_database,
const ClientInfo & client_info) const
{
return context_access_cache->getContextAccess(user_id, current_roles, use_default_roles, settings, current_database, client_info);
ContextAccessParams params;
params.user_id = user_id;
params.current_roles = current_roles;
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
params.allow_ddl = settings.allow_ddl;
params.allow_introspection = settings.allow_introspection_functions;
params.interface = client_info.interface;
params.http_method = client_info.http_method;
params.address = client_info.current_address.host();
params.quota_key = client_info.quota_key;
return getContextAccess(params);
}
std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(const ContextAccessParams & params) const
{
return context_access_cache->getContextAccess(params);
}

View File

@ -21,6 +21,7 @@ namespace Poco
namespace DB
{
class ContextAccess;
struct ContextAccessParams;
struct User;
using UserPtr = std::shared_ptr<const User>;
class EnabledRoles;
@ -58,6 +59,8 @@ public:
const String & current_database,
const ClientInfo & client_info) const;
std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params) const;
std::shared_ptr<const EnabledRoles> getEnabledRoles(
const boost::container::flat_set<UUID> & current_roles,
const boost::container::flat_set<UUID> & current_roles_with_admin_option) const;

View File

@ -1,7 +1,9 @@
#include <Access/AccessRights.h>
#include <Common/Exception.h>
#include <common/logger_useful.h>
#include <boost/container/small_vector.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <unordered_map>
namespace DB
@ -9,7 +11,6 @@ namespace DB
namespace ErrorCodes
{
extern const int INVALID_GRANT;
extern const int LOGICAL_ERROR;
}
@ -58,12 +59,194 @@ namespace
const AccessFlags system_reload_embedded_dictionaries = AccessType::SYSTEM_RELOAD_EMBEDDED_DICTIONARIES;
};
std::string_view checkCurrentDatabase(const std::string_view & current_database)
using Kind = AccessRightsElementWithOptions::Kind;
struct ProtoElement
{
if (current_database.empty())
throw Exception("No current database", ErrorCodes::LOGICAL_ERROR);
return current_database;
}
AccessFlags access_flags;
boost::container::small_vector<std::string_view, 3> full_name;
bool grant_option = false;
Kind kind = Kind::GRANT;
friend bool operator<(const ProtoElement & left, const ProtoElement & right)
{
static constexpr auto compare_name = [](const boost::container::small_vector<std::string_view, 3> & left_name,
const boost::container::small_vector<std::string_view, 3> & right_name,
size_t i)
{
if (i < left_name.size())
{
if (i < right_name.size())
return left_name[i].compare(right_name[i]);
else
return 1; /// left_name is longer => left_name > right_name
}
else if (i < right_name.size())
return 1; /// right_name is longer => left < right
else
return 0; /// left_name == right_name
};
if (int cmp = compare_name(left.full_name, right.full_name, 0))
return cmp < 0;
if (int cmp = compare_name(left.full_name, right.full_name, 1))
return cmp < 0;
if (left.kind != right.kind)
return (left.kind == Kind::GRANT);
if (left.grant_option != right.grant_option)
return right.grant_option;
if (int cmp = compare_name(left.full_name, right.full_name, 2))
return cmp < 0;
return (left.access_flags < right.access_flags);
}
AccessRightsElementWithOptions getResult() const
{
AccessRightsElementWithOptions res;
res.access_flags = access_flags;
res.grant_option = grant_option;
res.kind = kind;
switch (full_name.size())
{
case 0:
{
res.any_database = true;
res.any_table = true;
res.any_column = true;
break;
}
case 1:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = true;
res.any_column = true;
break;
}
case 2:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = false;
res.table = full_name[1];
res.any_column = true;
break;
}
case 3:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = false;
res.table = full_name[1];
res.any_column = false;
res.columns.emplace_back(full_name[2]);
break;
}
}
return res;
}
};
class ProtoElements : public std::vector<ProtoElement>
{
public:
AccessRightsElementsWithOptions getResult() const
{
ProtoElements sorted = *this;
boost::range::sort(sorted);
AccessRightsElementsWithOptions res;
res.reserve(sorted.size());
for (size_t i = 0; i != sorted.size();)
{
size_t count_elements_with_diff_columns = sorted.countElementsWithDifferenceInColumnOnly(i);
if (count_elements_with_diff_columns == 1)
{
/// Easy case: one Element is converted to one AccessRightsElement.
const auto & element = sorted[i];
if (element.access_flags)
res.emplace_back(element.getResult());
++i;
}
else
{
/// Difficult case: multiple Elements are converted to one or multiple AccessRightsElements.
sorted.appendResultWithElementsWithDifferenceInColumnOnly(i, count_elements_with_diff_columns, res);
i += count_elements_with_diff_columns;
}
}
return res;
}
private:
size_t countElementsWithDifferenceInColumnOnly(size_t start) const
{
const auto & start_element = (*this)[start];
if ((start_element.full_name.size() != 3) || (start == size() - 1))
return 1;
auto it = std::find_if(begin() + start + 1, end(), [&](const ProtoElement & element)
{
return (element.full_name.size() != 3) || (element.full_name[0] != start_element.full_name[0])
|| (element.full_name[1] != start_element.full_name[1]) || (element.grant_option != start_element.grant_option)
|| (element.kind != start_element.kind);
});
return it - (begin() + start);
}
/// Collects columns together to write multiple columns into one AccessRightsElement.
/// That procedure allows to output access rights in more compact way,
/// e.g. "SELECT(x, y)" instead of "SELECT(x), SELECT(y)".
void appendResultWithElementsWithDifferenceInColumnOnly(size_t start, size_t count, AccessRightsElementsWithOptions & res) const
{
const auto * pbegin = data() + start;
const auto * pend = pbegin + count;
AccessFlags handled_flags;
while (pbegin < pend)
{
while (pbegin < pend && !(pbegin->access_flags - handled_flags))
++pbegin;
while (pbegin < pend && !((pend - 1)->access_flags - handled_flags))
--pend;
if (pbegin >= pend)
break;
AccessFlags common_flags = (pbegin->access_flags - handled_flags);
for (const auto * element = pbegin + 1; element != pend; ++element)
{
if (auto new_common_flags = (element->access_flags - handled_flags) & common_flags)
common_flags = new_common_flags;
}
res.emplace_back();
auto & back = res.back();
back.grant_option = pbegin->grant_option;
back.kind = pbegin->kind;
back.any_database = false;
back.database = pbegin->full_name[0];
back.any_table = false;
back.table = pbegin->full_name[1];
back.any_column = false;
back.access_flags = common_flags;
for (const auto * element = pbegin; element != pend; ++element)
{
if (((element->access_flags - handled_flags) & common_flags) == common_flags)
back.columns.emplace_back(element->full_name[2]);
}
handled_flags |= common_flags;
}
}
};
}
@ -249,17 +432,32 @@ public:
calculateFinalAccessRec(helper);
}
void logTree(Poco::Logger * log) const
ProtoElements getElements() const
{
LOG_TRACE(log, "Tree({}): name={}, access={}, final_access={}, min_access={}, max_access={}, num_children={}",
level, node_name ? *node_name : "NULL", access.toString(),
ProtoElements res;
getElementsRec(res, {}, *this, {});
return res;
}
static ProtoElements getElements(const Node * node, const Node * node_with_grant_option)
{
ProtoElements res;
getElementsRec(res, {}, node, {}, node_with_grant_option, {});
return res;
}
void logTree(Poco::Logger * log, const String & title) const
{
LOG_TRACE(log, "Tree({}): level={}, name={}, access={}, final_access={}, min_access={}, max_access={}, num_children={}",
title, level, node_name ? *node_name : "NULL", access.toString(),
final_access.toString(), min_access.toString(), max_access.toString(),
(children ? children->size() : 0));
if (children)
{
for (auto & child : *children | boost::adaptors::map_values)
child.logTree(log);
child.logTree(log, title);
}
}
@ -342,6 +540,93 @@ private:
}
}
static void getElementsRec(
ProtoElements & res,
const boost::container::small_vector<std::string_view, 3> & full_name,
const Node & node,
const AccessFlags & parent_access)
{
auto access = node.access;
auto revokes = parent_access - access;
auto grants = access - parent_access;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
if (node.children)
{
for (const auto & [child_name, child] : *node.children)
{
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
getElementsRec(res, child_full_name, child, access);
}
}
}
static void getElementsRec(
ProtoElements & res,
const boost::container::small_vector<std::string_view, 3> & full_name,
const Node * node,
const AccessFlags & parent_access,
const Node * node_go,
const AccessFlags & parent_access_go)
{
auto access = node ? node->access : parent_access;
auto access_go = node_go ? node_go->access : parent_access_go;
auto revokes = parent_access - access;
auto revokes_go = parent_access_go - access_go - revokes;
auto grants_go = access_go - parent_access_go;
auto grants = access - parent_access - grants_go;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
if (revokes_go)
res.push_back(ProtoElement{revokes_go, full_name, true, Kind::REVOKE});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
if (grants_go)
res.push_back(ProtoElement{grants_go, full_name, true, Kind::GRANT});
if (node && node->children)
{
for (const auto & [child_name, child] : *node->children)
{
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
const Node * child_node = &child;
const Node * child_node_go = nullptr;
if (node_go && node_go->children)
{
auto it = node_go->children->find(child_name);
if (it != node_go->children->end())
child_node_go = &it->second;
}
getElementsRec(res, child_full_name, child_node, access, child_node_go, access_go);
}
}
if (node_go && node_go->children)
{
for (const auto & [child_name, child] : *node_go->children)
{
if (node && node->children && node->children->count(child_name))
continue; /// already processed
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
const Node * child_node = nullptr;
const Node * child_node_go = &child;
getElementsRec(res, child_full_name, child_node, access, child_node_go, access_go);
}
}
}
void calculateFinalAccessRec(const Helper & helper)
{
/// Traverse tree.
@ -476,6 +761,10 @@ AccessRights & AccessRights::operator =(const AccessRights & src)
root = std::make_unique<Node>(*src.root);
else
root = nullptr;
if (src.root_with_grant_option)
root_with_grant_option = std::make_unique<Node>(*src.root_with_grant_option);
else
root_with_grant_option = nullptr;
return *this;
}
@ -488,302 +777,245 @@ AccessRights::AccessRights(const AccessFlags & access)
bool AccessRights::isEmpty() const
{
return !root;
return !root && !root_with_grant_option;
}
void AccessRights::clear()
{
root = nullptr;
root_with_grant_option = nullptr;
}
template <typename... Args>
template <bool with_grant_option, typename... Args>
void AccessRights::grantImpl(const AccessFlags & flags, const Args &... args)
{
if (!root)
root = std::make_unique<Node>();
root->grant(flags, Helper::instance(), args...);
if (!root->access && !root->children)
root = nullptr;
auto helper = [&](std::unique_ptr<Node> & root_node)
{
if (!root_node)
root_node = std::make_unique<Node>();
root_node->grant(flags, Helper::instance(), args...);
if (!root_node->access && !root_node->children)
root_node = nullptr;
};
helper(root);
if constexpr (with_grant_option)
helper(root_with_grant_option);
}
void AccessRights::grant(const AccessFlags & flags) { grantImpl(flags); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database) { grantImpl(flags, database); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl(flags, database, table); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl(flags, database, table, column); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl(flags, database, table, columns); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl(flags, database, table, columns); }
void AccessRights::grant(const AccessRightsElement & element, std::string_view current_database)
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElement & element)
{
if (element.any_database)
{
grant(element.access_flags);
}
grantImpl<with_grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database));
else
grant(element.access_flags, element.database);
}
grantImpl<with_grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database), element.table);
else
grant(element.access_flags, element.database, element.table);
}
grantImpl<with_grant_option>(element.access_flags, element.database, element.table);
else
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
grant(element.access_flags, element.database, element.table, element.columns);
}
grantImpl<with_grant_option>(element.access_flags, element.database, element.table, element.columns);
}
void AccessRights::grant(const AccessRightsElements & elements, std::string_view current_database)
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElements & elements)
{
for (const auto & element : elements)
grant(element, current_database);
grantImpl<with_grant_option>(element);
}
void AccessRights::grant(const AccessFlags & flags) { grantImpl<false>(flags); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database) { grantImpl<false>(flags, database); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl<false>(flags, database, table); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl<false>(flags, database, table, column); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl<false>(flags, database, table, columns); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl<false>(flags, database, table, columns); }
void AccessRights::grant(const AccessRightsElement & element) { grantImpl<false>(element); }
void AccessRights::grant(const AccessRightsElements & elements) { grantImpl<false>(elements); }
template <typename... Args>
void AccessRights::grantWithGrantOption(const AccessFlags & flags) { grantImpl<true>(flags); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database) { grantImpl<true>(flags, database); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl<true>(flags, database, table); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl<true>(flags, database, table, column); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl<true>(flags, database, table, columns); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl<true>(flags, database, table, columns); }
void AccessRights::grantWithGrantOption(const AccessRightsElement & element) { grantImpl<true>(element); }
void AccessRights::grantWithGrantOption(const AccessRightsElements & elements) { grantImpl<true>(elements); }
template <bool grant_option, typename... Args>
void AccessRights::revokeImpl(const AccessFlags & flags, const Args &... args)
{
if (!root)
return;
root->revoke(flags, Helper::instance(), args...);
if (!root->access && !root->children)
root = nullptr;
auto helper = [&](std::unique_ptr<Node> & root_node)
{
if (!root_node)
return;
root_node->revoke(flags, Helper::instance(), args...);
if (!root_node->access && !root_node->children)
root_node = nullptr;
};
helper(root_with_grant_option);
if constexpr (!grant_option)
helper(root);
}
void AccessRights::revoke(const AccessFlags & flags) { revokeImpl(flags); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database) { revokeImpl(flags, database); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl(flags, database, table); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl(flags, database, table, column); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl(flags, database, table, columns); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl(flags, database, table, columns); }
void AccessRights::revoke(const AccessRightsElement & element, std::string_view current_database)
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElement & element)
{
if (element.any_database)
{
revoke(element.access_flags);
}
revokeImpl<grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database));
else
revoke(element.access_flags, element.database);
}
revokeImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database), element.table);
else
revoke(element.access_flags, element.database, element.table);
}
revokeImpl<grant_option>(element.access_flags, element.database, element.table);
else
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
revoke(element.access_flags, element.database, element.table, element.columns);
}
revokeImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
void AccessRights::revoke(const AccessRightsElements & elements, std::string_view current_database)
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElements & elements)
{
for (const auto & element : elements)
revoke(element, current_database);
revokeImpl<grant_option>(element);
}
void AccessRights::revoke(const AccessFlags & flags) { revokeImpl<false>(flags); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database) { revokeImpl<false>(flags, database); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl<false>(flags, database, table); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl<false>(flags, database, table, column); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl<false>(flags, database, table, columns); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl<false>(flags, database, table, columns); }
void AccessRights::revoke(const AccessRightsElement & element) { revokeImpl<false>(element); }
void AccessRights::revoke(const AccessRightsElements & elements) { revokeImpl<false>(elements); }
AccessRightsElements AccessRights::getGrants() const
{
AccessRightsElements grants;
getGrantsAndPartialRevokesImpl(&grants, nullptr);
return grants;
}
AccessRightsElements AccessRights::getPartialRevokes() const
{
AccessRightsElements partial_revokes;
getGrantsAndPartialRevokesImpl(nullptr, &partial_revokes);
return partial_revokes;
}
AccessRights::GrantsAndPartialRevokes AccessRights::getGrantsAndPartialRevokes() const
{
GrantsAndPartialRevokes res;
getGrantsAndPartialRevokesImpl(&res.grants, &res.revokes);
return res;
}
void AccessRights::getGrantsAndPartialRevokesImpl(AccessRightsElements * out_grants, AccessRightsElements * out_partial_revokes) const
void AccessRights::revokeGrantOption(const AccessFlags & flags) { revokeImpl<true>(flags); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database) { revokeImpl<true>(flags, database); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl<true>(flags, database, table); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl<true>(flags, database, table, column); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl<true>(flags, database, table, columns); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl<true>(flags, database, table, columns); }
void AccessRights::revokeGrantOption(const AccessRightsElement & element) { revokeImpl<true>(element); }
void AccessRights::revokeGrantOption(const AccessRightsElements & elements) { revokeImpl<true>(elements); }
AccessRightsElementsWithOptions AccessRights::getElements() const
{
#if 0
logTree();
#endif
if (!root)
return;
auto global_access = root->access;
if (out_grants && global_access)
out_grants->push_back({global_access});
if (root->children)
{
for (const auto & [db_name, db_node] : *root->children)
{
if (out_grants)
{
if (auto db_grants = db_node.access - global_access)
out_grants->push_back({db_grants, db_name});
}
if (out_partial_revokes)
{
if (auto db_partial_revokes = global_access - db_node.access)
out_partial_revokes->push_back({db_partial_revokes, db_name});
}
if (db_node.children)
{
for (const auto & [table_name, table_node] : *db_node.children)
{
if (out_grants)
{
if (auto table_grants = table_node.access - db_node.access)
out_grants->push_back({table_grants, db_name, table_name});
}
if (out_partial_revokes)
{
if (auto table_partial_revokes = db_node.access - table_node.access)
out_partial_revokes->push_back({table_partial_revokes, db_name, table_name});
}
if (table_node.children)
{
for (const auto & [column_name, column_node] : *table_node.children)
{
if (out_grants)
{
if (auto column_grants = column_node.access - table_node.access)
out_grants->push_back({column_grants, db_name, table_name, column_name});
}
if (out_partial_revokes)
{
if (auto column_partial_revokes = table_node.access - column_node.access)
out_partial_revokes->push_back({column_partial_revokes, db_name, table_name, column_name});
}
}
}
}
}
}
}
return {};
if (!root_with_grant_option)
return root->getElements().getResult();
return Node::getElements(root.get(), root_with_grant_option.get()).getResult();
}
String AccessRights::toString() const
{
String res;
auto gr = getGrantsAndPartialRevokes();
if (!gr.grants.empty())
{
res += "GRANT ";
res += gr.grants.toString();
}
if (!gr.revokes.empty())
{
if (!res.empty())
res += ", ";
res += "REVOKE ";
res += gr.revokes.toString();
}
if (res.empty())
res = "GRANT USAGE ON *.*";
return res;
return getElements().toString();
}
template <typename... Args>
template <bool grant_option, typename... Args>
bool AccessRights::isGrantedImpl(const AccessFlags & flags, const Args &... args) const
{
if (!root)
return flags.isEmpty();
return root->isGranted(flags, args...);
auto helper = [&](const std::unique_ptr<Node> & root_node) -> bool
{
if (!root_node)
return flags.isEmpty();
return root_node->isGranted(flags, args...);
};
if constexpr (grant_option)
return helper(root_with_grant_option);
else
return helper(root);
}
bool AccessRights::isGranted(const AccessFlags & flags) const { return isGrantedImpl(flags); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl(flags, database); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl(flags, database, table); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl(flags, database, table, column); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessRightsElement & element, std::string_view current_database) const
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElement & element) const
{
if (element.any_database)
{
return isGranted(element.access_flags);
}
return isGrantedImpl<grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database));
else
return isGranted(element.access_flags, element.database);
}
return isGrantedImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database), element.table);
else
return isGranted(element.access_flags, element.database, element.table);
}
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table);
else
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
return isGranted(element.access_flags, element.database, element.table, element.columns);
}
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
bool AccessRights::isGranted(const AccessRightsElements & elements, std::string_view current_database) const
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!isGranted(element, current_database))
if (!isGrantedImpl<grant_option>(element))
return false;
return true;
}
bool AccessRights::isGranted(const AccessFlags & flags) const { return isGrantedImpl<false>(flags); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<false>(flags, database); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<false>(flags, database, table); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<false>(flags, database, table, column); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessRightsElement & element) const { return isGrantedImpl<false>(element); }
bool AccessRights::isGranted(const AccessRightsElements & elements) const { return isGrantedImpl<false>(elements); }
bool AccessRights::hasGrantOption(const AccessFlags & flags) const { return isGrantedImpl<true>(flags); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<true>(flags, database); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<true>(flags, database, table); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<true>(flags, database, table, column); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool AccessRights::hasGrantOption(const AccessRightsElement & element) const { return isGrantedImpl<true>(element); }
bool AccessRights::hasGrantOption(const AccessRightsElements & elements) const { return isGrantedImpl<true>(elements); }
bool operator ==(const AccessRights & left, const AccessRights & right)
{
if (!left.root)
return !right.root;
if (!right.root)
return false;
return *left.root == *right.root;
auto helper = [](const std::unique_ptr<AccessRights::Node> & left_node, const std::unique_ptr<AccessRights::Node> & right_node)
{
if (!left_node)
return !right_node;
if (!right_node)
return false;
return *left_node == *right_node;
};
return helper(left.root, right.root) && helper(left.root_with_grant_option, right.root_with_grant_option);
}
void AccessRights::merge(const AccessRights & other)
{
if (!root)
auto helper = [](std::unique_ptr<Node> & root_node, const std::unique_ptr<Node> & other_root_node)
{
*this = other;
return;
}
if (other.root)
{
root->merge(*other.root, Helper::instance());
if (!root->access && !root->children)
root = nullptr;
}
if (!root_node)
{
if (other_root_node)
root_node = std::make_unique<Node>(*other_root_node);
return;
}
if (other_root_node)
{
root_node->merge(*other_root_node, Helper::instance());
if (!root_node->access && !root_node->children)
root_node = nullptr;
}
};
helper(root, other.root);
helper(root_with_grant_option, other.root_with_grant_option);
}
AccessRights AccessRights::getFullAccess()
{
AccessRights res;
res.grantWithGrantOption(AccessType::ALL);
return res;
}
@ -791,7 +1023,11 @@ void AccessRights::logTree() const
{
auto * log = &Poco::Logger::get("AccessRights");
if (root)
root->logTree(log);
{
root->logTree(log, "");
if (root_with_grant_option)
root->logTree(log, "go");
}
else
LOG_TRACE(log, "Tree: NULL");
}

View File

@ -26,6 +26,12 @@ public:
/// Revokes everything. It's the same as revoke(AccessType::ALL).
void clear();
/// Returns the information about all the access granted as a string.
String toString() const;
/// Returns the information about all the access granted.
AccessRightsElementsWithOptions getElements() const;
/// Grants access on a specified database/table/column.
/// Does nothing if the specified access has been already granted.
void grant(const AccessFlags & flags);
@ -34,8 +40,17 @@ public:
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void grant(const AccessRightsElement & element, std::string_view current_database = {});
void grant(const AccessRightsElements & elements, std::string_view current_database = {});
void grant(const AccessRightsElement & element);
void grant(const AccessRightsElements & elements);
void grantWithGrantOption(const AccessFlags & flags);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void grantWithGrantOption(const AccessRightsElement & element);
void grantWithGrantOption(const AccessRightsElements & elements);
/// Revokes a specified access granted earlier on a specified database/table/column.
/// For example, revoke(AccessType::ALL) revokes all grants at all, just like clear();
@ -45,21 +60,17 @@ public:
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void revoke(const AccessRightsElement & element, std::string_view current_database = {});
void revoke(const AccessRightsElements & elements, std::string_view current_database = {});
void revoke(const AccessRightsElement & element);
void revoke(const AccessRightsElements & elements);
/// Returns the information about all the access granted.
struct GrantsAndPartialRevokes
{
AccessRightsElements grants;
AccessRightsElements revokes;
};
AccessRightsElements getGrants() const;
AccessRightsElements getPartialRevokes() const;
GrantsAndPartialRevokes getGrantsAndPartialRevokes() const;
/// Returns the information about all the access granted as a string.
String toString() const;
void revokeGrantOption(const AccessFlags & flags);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void revokeGrantOption(const AccessRightsElement & element);
void revokeGrantOption(const AccessRightsElements & elements);
/// Whether a specified access granted.
bool isGranted(const AccessFlags & flags) const;
@ -68,38 +79,60 @@ public:
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool isGranted(const AccessRightsElement & element, std::string_view current_database = {}) const;
bool isGranted(const AccessRightsElements & elements, std::string_view current_database = {}) const;
bool isGranted(const AccessRightsElement & element) const;
bool isGranted(const AccessRightsElements & elements) const;
friend bool operator ==(const AccessRights & left, const AccessRights & right);
friend bool operator !=(const AccessRights & left, const AccessRights & right) { return !(left == right); }
bool hasGrantOption(const AccessFlags & flags) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool hasGrantOption(const AccessRightsElement & element) const;
bool hasGrantOption(const AccessRightsElements & elements) const;
/// Merges two sets of access rights together.
/// It's used to combine access rights from multiple roles.
void merge(const AccessRights & other);
friend bool operator ==(const AccessRights & left, const AccessRights & right);
friend bool operator !=(const AccessRights & left, const AccessRights & right) { return !(left == right); }
static AccessRights getFullAccess();
private:
template <typename... Args>
template <bool with_grant_option, typename... Args>
void grantImpl(const AccessFlags & flags, const Args &... args);
template <typename... Args>
template <bool with_grant_options>
void grantImpl(const AccessRightsElement & element);
template <bool with_grant_options>
void grantImpl(const AccessRightsElements & elements);
template <bool grant_option, typename... Args>
void revokeImpl(const AccessFlags & flags, const Args &... args);
template <typename... Args>
template <bool grant_option>
void revokeImpl(const AccessRightsElement & element);
template <bool grant_option>
void revokeImpl(const AccessRightsElements & elements);
template <bool grant_option, typename... Args>
bool isGrantedImpl(const AccessFlags & flags, const Args &... args) const;
bool isGrantedImpl(const AccessRightsElement & element, std::string_view current_database) const;
bool isGrantedImpl(const AccessRightsElements & elements, std::string_view current_database) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElement & element) const;
template <typename... Args>
AccessFlags getAccessImpl(const Args &... args) const;
void getGrantsAndPartialRevokesImpl(AccessRightsElements * grants, AccessRightsElements * partial_revokes) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElements & elements) const;
void logTree() const;
struct Node;
std::unique_ptr<Node> root;
std::unique_ptr<Node> root_with_grant_option;
};
}

View File

@ -12,222 +12,158 @@ namespace DB
{
namespace
{
size_t groupElements(AccessRightsElements & elements, size_t start)
using Kind = AccessRightsElementWithOptions::Kind;
String formatOptions(bool grant_option, Kind kind, const String & inner_part)
{
auto & start_element = elements[start];
auto it = std::find_if(elements.begin() + start + 1, elements.end(),
[&](const AccessRightsElement & element)
if (kind == Kind::REVOKE)
{
return (element.database != start_element.database) ||
(element.any_database != start_element.any_database) ||
(element.table != start_element.table) ||
(element.any_table != start_element.any_table) ||
(element.any_column != start_element.any_column);
});
size_t end = it - elements.begin();
/// All the elements at indices from start to end here specify
/// the same database and table.
if (start_element.any_column)
{
/// Easy case: the elements don't specify columns.
/// All we need is to combine the access flags.
for (size_t i = start + 1; i != end; ++i)
{
start_element.access_flags |= elements[i].access_flags;
elements[i].access_flags = {};
}
return end;
if (grant_option)
return "REVOKE GRANT OPTION " + inner_part;
else
return "REVOKE " + inner_part;
}
/// Difficult case: the elements specify columns.
/// We have to find groups of columns with common access flags.
for (size_t i = start; i != end; ++i)
else
{
if (!elements[i].access_flags)
continue;
AccessFlags common_flags = elements[i].access_flags;
size_t num_elements_with_common_flags = 1;
for (size_t j = i + 1; j != end; ++j)
{
auto new_common_flags = common_flags & elements[j].access_flags;
if (new_common_flags)
{
common_flags = new_common_flags;
++num_elements_with_common_flags;
}
}
if (num_elements_with_common_flags == 1)
continue;
if (elements[i].access_flags != common_flags)
{
elements.insert(elements.begin() + i + 1, elements[i]);
elements[i].access_flags = common_flags;
elements[i].columns.clear();
++end;
}
for (size_t j = i + 1; j != end; ++j)
{
if ((elements[j].access_flags & common_flags) == common_flags)
{
boost::range::push_back(elements[i].columns, elements[j].columns);
elements[j].access_flags -= common_flags;
}
}
if (grant_option)
return "GRANT " + inner_part + " WITH GRANT OPTION";
else
return "GRANT " + inner_part;
}
return end;
}
/// Tries to combine elements to decrease their number.
void groupElements(AccessRightsElements & elements)
String formatONClause(const String & database, bool any_database, const String & table, bool any_table)
{
if (!boost::range::is_sorted(elements))
boost::range::sort(elements); /// Algorithm in groupElement() requires elements to be sorted.
for (size_t start = 0; start != elements.size();)
start = groupElements(elements, start);
String msg = "ON ";
if (any_database)
msg += "*.";
else if (!database.empty())
msg += backQuoteIfNeed(database) + ".";
if (any_table)
msg += "*";
else
msg += backQuoteIfNeed(table);
return msg;
}
/// Removes unnecessary elements, sorts elements and makes them unique.
void sortElementsAndMakeUnique(AccessRightsElements & elements)
String formatAccessFlagsWithColumns(const AccessFlags & access_flags, const Strings & columns, bool any_column)
{
/// Remove empty elements.
boost::range::remove_erase_if(elements, [](const AccessRightsElement & element)
String columns_in_parentheses;
if (!any_column)
{
return !element.access_flags || (!element.any_column && element.columns.empty());
});
/// Sort columns and make them unique.
for (auto & element : elements)
{
if (element.any_column)
continue;
if (!boost::range::is_sorted(element.columns))
boost::range::sort(element.columns);
element.columns.erase(std::unique(element.columns.begin(), element.columns.end()), element.columns.end());
if (columns.empty())
return "USAGE";
for (const auto & column : columns)
{
columns_in_parentheses += columns_in_parentheses.empty() ? "(" : ", ";
columns_in_parentheses += backQuoteIfNeed(column);
}
columns_in_parentheses += ")";
}
/// Sort elements themselves.
boost::range::sort(elements);
elements.erase(std::unique(elements.begin(), elements.end()), elements.end());
auto keywords = access_flags.toKeywords();
if (keywords.empty())
return "USAGE";
String msg;
for (const std::string_view & keyword : keywords)
{
if (!msg.empty())
msg += ", ";
msg += String{keyword} + columns_in_parentheses;
}
return msg;
}
}
void AccessRightsElement::setDatabase(const String & new_database)
{
database = new_database;
any_database = false;
}
void AccessRightsElement::replaceEmptyDatabase(const String & new_database)
{
if (isEmptyDatabase())
setDatabase(new_database);
}
bool AccessRightsElement::isEmptyDatabase() const
{
return !any_database && database.empty();
}
String AccessRightsElement::toString() const
{
String msg = toStringWithoutON();
msg += " ON ";
if (any_database)
msg += "*.";
else if (!database.empty())
msg += backQuoteIfNeed(database) + ".";
if (any_table)
msg += "*";
else
msg += backQuoteIfNeed(table);
return msg;
return formatAccessFlagsWithColumns(access_flags, columns, any_column) + " " + formatONClause(database, any_database, table, any_table);
}
String AccessRightsElement::toStringWithoutON() const
String AccessRightsElementWithOptions::toString() const
{
String columns_in_parentheses;
if (!any_column)
{
if (columns.empty())
return "USAGE";
for (const auto & column : columns)
{
columns_in_parentheses += columns_in_parentheses.empty() ? "(" : ", ";
columns_in_parentheses += backQuoteIfNeed(column);
}
columns_in_parentheses += ")";
}
auto keywords = access_flags.toKeywords();
if (keywords.empty())
return "USAGE";
String msg;
for (const std::string_view & keyword : keywords)
{
if (!msg.empty())
msg += ", ";
msg += String{keyword} + columns_in_parentheses;
}
return msg;
return formatOptions(grant_option, kind, AccessRightsElement::toString());
}
void AccessRightsElements::replaceEmptyDatabase(const String & new_database)
String AccessRightsElements::toString() const
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
String AccessRightsElements::toString()
{
normalize();
if (empty())
return "USAGE ON *.*";
String msg;
bool need_comma = false;
String res;
String inner_part;
for (size_t i = 0; i != size(); ++i)
{
const auto & element = (*this)[i];
if (std::exchange(need_comma, true))
msg += ", ";
bool next_element_on_same_db_and_table = false;
if (!inner_part.empty())
inner_part += ", ";
inner_part += formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column);
bool next_element_uses_same_table = false;
if (i != size() - 1)
{
const auto & next_element = (*this)[i + 1];
if ((element.database == next_element.database) && (element.any_database == next_element.any_database)
&& (element.table == next_element.table) && (element.any_table == next_element.any_table))
next_element_on_same_db_and_table = true;
if (element.sameDatabaseAndTable(next_element))
next_element_uses_same_table = true;
}
if (!next_element_uses_same_table)
{
if (!res.empty())
res += ", ";
res += inner_part + " " + formatONClause(element.database, element.any_database, element.table, element.any_table);
inner_part.clear();
}
if (next_element_on_same_db_and_table)
msg += element.toStringWithoutON();
else
msg += element.toString();
}
return msg;
return res;
}
void AccessRightsElements::normalize()
String AccessRightsElementsWithOptions::toString() const
{
groupElements(*this);
sortElementsAndMakeUnique(*this);
if (empty())
return "GRANT USAGE ON *.*";
String res;
String inner_part;
for (size_t i = 0; i != size(); ++i)
{
const auto & element = (*this)[i];
if (!inner_part.empty())
inner_part += ", ";
inner_part += formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column);
bool next_element_uses_same_mode_and_table = false;
if (i != size() - 1)
{
const auto & next_element = (*this)[i + 1];
if (element.sameDatabaseAndTable(next_element) && element.sameOptions(next_element))
next_element_uses_same_mode_and_table = true;
}
if (!next_element_uses_same_mode_and_table)
{
if (!res.empty())
res += ", ";
res += formatOptions(
element.grant_option,
element.kind,
inner_part + " " + formatONClause(element.database, element.any_database, element.table, element.any_table));
inner_part.clear();
}
}
return res;
}
}

View File

@ -71,26 +71,48 @@ struct AccessRightsElement
{
}
auto toTuple() const { return std::tie(access_flags, database, any_database, table, any_table, columns, any_column); }
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns); }
friend bool operator==(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() == right.toTuple(); }
friend bool operator!=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() != right.toTuple(); }
friend bool operator<(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() < right.toTuple(); }
friend bool operator>(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() > right.toTuple(); }
friend bool operator<=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() <= right.toTuple(); }
friend bool operator>=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() >= right.toTuple(); }
friend bool operator!=(const AccessRightsElement & left, const AccessRightsElement & right) { return !(left == right); }
/// Sets the database.
void setDatabase(const String & new_database);
bool sameDatabaseAndTable(const AccessRightsElement & other) const
{
return (database == other.database) && (any_database == other.any_database) && (table == other.table)
&& (any_table == other.any_table);
}
bool isEmptyDatabase() const { return !any_database && database.empty(); }
/// If the database is empty, replaces it with `new_database`. Otherwise does nothing.
void replaceEmptyDatabase(const String & new_database);
bool isEmptyDatabase() const;
/// Returns a human-readable representation like "SELECT, UPDATE(x, y) ON db.table".
/// The returned string isn't prefixed with the "GRANT" keyword.
String toString() const;
String toStringWithoutON() const;
};
struct AccessRightsElementWithOptions : public AccessRightsElement
{
bool grant_option = false;
enum class Kind
{
GRANT,
REVOKE,
};
Kind kind = Kind::GRANT;
bool sameOptions(const AccessRightsElementWithOptions & other) const
{
return (grant_option == other.grant_option) && (kind == other.kind);
}
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns, grant_option, kind); }
friend bool operator==(const AccessRightsElementWithOptions & left, const AccessRightsElementWithOptions & right) { return left.toTuple() == right.toTuple(); }
friend bool operator!=(const AccessRightsElementWithOptions & left, const AccessRightsElementWithOptions & right) { return !(left == right); }
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
@ -101,13 +123,38 @@ public:
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
/// Returns a human-readable representation like "SELECT, UPDATE(x, y) ON db.table".
/// The returned string isn't prefixed with the "GRANT" keyword.
String toString() const { return AccessRightsElements(*this).toString(); }
String toString();
/// Reorder and group elements to show them in more readable form.
void normalize();
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
class AccessRightsElementsWithOptions : public std::vector<AccessRightsElementWithOptions>
{
public:
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
inline void AccessRightsElement::replaceEmptyDatabase(const String & new_database)
{
if (isEmptyDatabase())
database = new_database;
}
inline void AccessRightsElements::replaceEmptyDatabase(const String & new_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
inline void AccessRightsElementsWithOptions::replaceEmptyDatabase(const String & new_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
}

View File

@ -15,8 +15,6 @@
#include <Poco/Logger.h>
#include <common/logger_useful.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/range/algorithm/fill.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>
#include <assert.h>
@ -32,68 +30,6 @@ namespace ErrorCodes
extern const int UNKNOWN_USER;
}
namespace
{
enum CheckAccessRightsMode
{
RETURN_FALSE_IF_ACCESS_DENIED,
LOG_WARNING_IF_ACCESS_DENIED,
THROW_IF_ACCESS_DENIED,
};
String formatSkippedMessage()
{
return "";
}
String formatSkippedMessage(const std::string_view & database)
{
return ". Skipped database " + backQuoteIfNeed(database);
}
String formatSkippedMessage(const std::string_view & database, const std::string_view & table)
{
String str = ". Skipped table ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
}
String formatSkippedMessage(const std::string_view & database, const std::string_view & table, const std::string_view & column)
{
String str = ". Skipped column " + backQuoteIfNeed(column) + " ON ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
}
template <typename StringT>
String formatSkippedMessage(const std::string_view & database, const std::string_view & table, const std::vector<StringT> & columns)
{
if (columns.size() == 1)
return formatSkippedMessage(database, table, columns[0]);
String str = ". Skipped columns ";
bool need_comma = false;
for (const auto & column : columns)
{
if (std::exchange(need_comma, true))
str += ", ";
str += backQuoteIfNeed(column);
}
str += " ON ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
}
}
ContextAccess::ContextAccess(const AccessControlManager & manager_, const Params & params_)
: manager(&manager_)
, params(params_)
@ -116,8 +52,8 @@ void ContextAccess::setUser(const UserPtr & user_) const
if (!user)
{
/// User has been dropped.
auto nothing_granted = boost::make_shared<AccessRights>();
boost::range::fill(result_access, nothing_granted);
auto nothing_granted = std::make_shared<AccessRights>();
access = nothing_granted;
subscription_for_user_change = {};
subscription_for_roles_changes = {};
enabled_roles = nullptr;
@ -169,10 +105,73 @@ void ContextAccess::setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> &
{
assert(roles_info_);
roles_info = roles_info_;
boost::range::fill(result_access, nullptr /* need recalculate */);
enabled_row_policies = manager->getEnabledRowPolicies(*params.user_id, roles_info->enabled_roles);
enabled_quota = manager->getEnabledQuota(*params.user_id, user_name, roles_info->enabled_roles, params.address, params.quota_key);
enabled_settings = manager->getEnabledSettings(*params.user_id, user->settings, roles_info->enabled_roles, roles_info->settings_from_enabled_roles);
setFinalAccess();
}
void ContextAccess::setFinalAccess() const
{
auto final_access = std::make_shared<AccessRights>();
*final_access = user->access;
if (roles_info)
final_access->merge(roles_info->access);
static const AccessFlags table_ddl = AccessType::CREATE_DATABASE | AccessType::CREATE_TABLE | AccessType::CREATE_VIEW
| AccessType::ALTER_TABLE | AccessType::ALTER_VIEW | AccessType::DROP_DATABASE | AccessType::DROP_TABLE | AccessType::DROP_VIEW
| AccessType::TRUNCATE;
static const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY;
static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl;
static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE;
static const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS;
if (params.readonly)
final_access->revoke(write_table_access | table_and_dictionary_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY);
if (params.readonly == 1)
{
/// Table functions are forbidden in readonly mode.
/// For example, for readonly = 2 - allowed.
final_access->revoke(AccessType::CREATE_TEMPORARY_TABLE);
}
if (!params.allow_ddl)
final_access->revoke(table_and_dictionary_ddl);
if (!params.allow_introspection)
final_access->revoke(AccessType::INTROSPECTION);
/// Anyone has access to the "system" database.
final_access->grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
if (params.readonly != 1)
{
/// User has access to temporary or external table if such table was resolved in session or query context
final_access->grant(AccessFlags::allTableFlags() | AccessFlags::allColumnFlags(), DatabaseCatalog::TEMPORARY_DATABASE);
}
if (params.readonly)
{
/// No grant option in readonly mode.
final_access->revokeGrantOption(AccessType::ALL);
}
if (trace_log)
{
if (roles_info && !roles_info->getCurrentRolesNames().empty())
{
LOG_TRACE(trace_log, "Current_roles: {}, enabled_roles: {}",
boost::algorithm::join(roles_info->getCurrentRolesNames(), ", "),
boost::algorithm::join(roles_info->getEnabledRolesNames(), ", "));
}
LOG_TRACE(trace_log, "Settings: readonly={}, allow_ddl={}, allow_introspection_functions={}", params.readonly, params.allow_ddl, params.allow_introspection);
LOG_TRACE(trace_log, "List of all grants: {}", final_access->toString());
}
access = final_access;
}
@ -193,284 +192,6 @@ bool ContextAccess::isClientHostAllowed() const
}
template <int mode, bool grant_option, typename... Args>
bool ContextAccess::calculateResultAccessAndCheck(Poco::Logger * log_, const AccessFlags & flags, const Args &... args) const
{
auto access = calculateResultAccess(grant_option);
bool is_granted = access->isGranted(flags, args...);
if (trace_log)
LOG_TRACE(trace_log, "Access {}: {}", (is_granted ? "granted" : "denied"), (AccessRightsElement{flags, args...}.toString()));
if (is_granted)
return true;
if constexpr (mode == RETURN_FALSE_IF_ACCESS_DENIED)
return false;
if constexpr (mode == LOG_WARNING_IF_ACCESS_DENIED)
{
if (!log_)
return false;
}
auto show_error = [&](const String & msg, [[maybe_unused]] int error_code)
{
if constexpr (mode == THROW_IF_ACCESS_DENIED)
throw Exception(user_name + ": " + msg, error_code);
else if constexpr (mode == LOG_WARNING_IF_ACCESS_DENIED)
LOG_WARNING(log_, "{}: {}{}", user_name, msg, formatSkippedMessage(args...));
};
if (!user)
{
show_error("User has been dropped", ErrorCodes::UNKNOWN_USER);
}
else if (grant_option && calculateResultAccess(false, params.readonly, params.allow_ddl, params.allow_introspection)->isGranted(flags, args...))
{
show_error(
"Not enough privileges. "
"The required privileges have been granted, but without grant option. "
"To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + " WITH GRANT OPTION",
ErrorCodes::ACCESS_DENIED);
}
else if (params.readonly && calculateResultAccess(false, false, params.allow_ddl, params.allow_introspection)->isGranted(flags, args...))
{
if (params.interface == ClientInfo::Interface::HTTP && params.http_method == ClientInfo::HTTPMethod::GET)
show_error(
"Cannot execute query in readonly mode. "
"For queries over HTTP, method GET implies readonly. You should use method POST for modifying queries",
ErrorCodes::READONLY);
else
show_error("Cannot execute query in readonly mode", ErrorCodes::READONLY);
}
else if (!params.allow_ddl && calculateResultAccess(false, params.readonly, true, params.allow_introspection)->isGranted(flags, args...))
{
show_error("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
}
else if (!params.allow_introspection && calculateResultAccess(false, params.readonly, params.allow_ddl, true)->isGranted(flags, args...))
{
show_error("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
}
else
{
show_error(
"Not enough privileges. To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""),
ErrorCodes::ACCESS_DENIED);
}
return false;
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags) const
{
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags);
}
template <int mode, bool grant_option, typename... Args>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
if (database.empty())
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags, params.current_database, args...);
else
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags, database, args...);
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessRightsElement & element) const
{
if (element.any_database)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags);
}
else if (element.any_table)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database);
}
else if (element.any_column)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database, element.table);
}
else
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database, element.table, element.columns);
}
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!checkAccessImpl<mode, grant_option>(log_, element))
return false;
return true;
}
void ContextAccess::checkAccess(const AccessFlags & flags) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, column); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessRightsElement & element) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, element); }
void ContextAccess::checkAccess(const AccessRightsElements & elements) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, elements); }
bool ContextAccess::isGranted(const AccessFlags & flags) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, column); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessRightsElement & element) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, element); }
bool ContextAccess::isGranted(const AccessRightsElements & elements) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, elements); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, column); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, columns); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, columns); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessRightsElement & element) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, element); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessRightsElements & elements) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, elements); }
void ContextAccess::checkGrantOption(const AccessFlags & flags) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, column); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessRightsElement & element) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, element); }
void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, elements); }
void ContextAccess::checkAdminOption(const UUID & role_id) const
{
if (isGranted(AccessType::ROLE_ADMIN))
return;
auto info = getRolesInfo();
if (info && info->enabled_roles_with_admin_option.count(role_id))
return;
if (!user)
throw Exception(user_name + ": User has been dropped", ErrorCodes::UNKNOWN_USER);
std::optional<String> role_name = manager->readName(role_id);
if (!role_name)
role_name = "ID {" + toString(role_id) + "}";
throw Exception(
getUserName() + ": Not enough privileges. To execute this query it's necessary to have the grant " + backQuoteIfNeed(*role_name)
+ " WITH ADMIN OPTION ",
ErrorCodes::ACCESS_DENIED);
}
boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool grant_option) const
{
return calculateResultAccess(grant_option, params.readonly, params.allow_ddl, params.allow_introspection);
}
boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const
{
size_t index = static_cast<size_t>(readonly_ != params.readonly)
+ static_cast<size_t>(allow_ddl_ != params.allow_ddl) * 2 +
+ static_cast<size_t>(allow_introspection_ != params.allow_introspection) * 3
+ static_cast<size_t>(grant_option) * 4;
assert(index < std::size(result_access));
auto res = result_access[index].load();
if (res)
return res;
std::lock_guard lock{mutex};
res = result_access[index].load();
if (res)
return res;
auto merged_access = boost::make_shared<AccessRights>();
if (grant_option)
{
*merged_access = user->access.access_with_grant_option;
if (roles_info)
merged_access->merge(roles_info->access_with_grant_option);
}
else
{
*merged_access = user->access.access;
if (roles_info)
merged_access->merge(roles_info->access);
}
static const AccessFlags table_ddl = AccessType::CREATE_DATABASE | AccessType::CREATE_TABLE | AccessType::CREATE_VIEW
| AccessType::ALTER_TABLE | AccessType::ALTER_VIEW | AccessType::DROP_DATABASE | AccessType::DROP_TABLE | AccessType::DROP_VIEW
| AccessType::TRUNCATE;
static const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY;
static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl;
static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE;
static const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS;
if (readonly_)
merged_access->revoke(write_table_access | table_and_dictionary_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY);
if (readonly_ == 1)
{
/// Table functions are forbidden in readonly mode.
/// For example, for readonly = 2 - allowed.
merged_access->revoke(AccessType::CREATE_TEMPORARY_TABLE);
}
if (!allow_ddl_)
merged_access->revoke(table_and_dictionary_ddl);
if (!allow_introspection_)
merged_access->revoke(AccessType::INTROSPECTION);
/// Anyone has access to the "system" database.
merged_access->grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
if (readonly_ != 1)
{
/// User has access to temporary or external table if such table was resolved in session or query context
merged_access->grant(AccessFlags::allTableFlags() | AccessFlags::allColumnFlags(), DatabaseCatalog::TEMPORARY_DATABASE);
}
if (readonly_ && grant_option)
{
/// No grant option in readonly mode.
merged_access->revoke(AccessType::ALL);
}
if (trace_log && (params.readonly == readonly_) && (params.allow_ddl == allow_ddl_) && (params.allow_introspection == allow_introspection_))
{
if (grant_option)
LOG_TRACE(trace_log, "List of all grants: {} WITH GRANT OPTION", merged_access->toString());
else
LOG_TRACE(trace_log, "List of all grants: {}", merged_access->toString());
if (roles_info && !roles_info->getCurrentRolesNames().empty())
{
LOG_TRACE(trace_log, "Current_roles: {}, enabled_roles: {}",
boost::algorithm::join(roles_info->getCurrentRolesNames(), ", "),
boost::algorithm::join(roles_info->getEnabledRolesNames(), ", "));
}
LOG_TRACE(trace_log, "Settings: readonly={}, allow_ddl={}, allow_introspection_functions={}", readonly_, allow_ddl_, allow_introspection_);
}
res = std::move(merged_access);
result_access[index].store(res);
return res;
}
UserPtr ContextAccess::getUser() const
{
std::lock_guard lock{mutex};
@ -520,9 +241,7 @@ std::shared_ptr<const ContextAccess> ContextAccess::getFullAccess()
static const std::shared_ptr<const ContextAccess> res = []
{
auto full_access = std::shared_ptr<ContextAccess>(new ContextAccess);
auto everything_granted = boost::make_shared<AccessRights>();
everything_granted->grant(AccessType::ALL);
boost::range::fill(full_access->result_access, everything_granted);
full_access->access = std::make_shared<AccessRights>(AccessRights::getFullAccess());
full_access->enabled_quota = EnabledQuota::getUnlimitedQuota();
return full_access;
}();
@ -543,4 +262,246 @@ std::shared_ptr<const SettingsConstraints> ContextAccess::getSettingsConstraints
return enabled_settings ? enabled_settings->getConstraints() : nullptr;
}
std::shared_ptr<const AccessRights> ContextAccess::getAccess() const
{
std::lock_guard lock{mutex};
return access;
}
template <bool grant_option, typename... Args>
bool ContextAccess::isGrantedImpl2(const AccessFlags & flags, const Args &... args) const
{
bool access_granted;
if constexpr (grant_option)
access_granted = getAccess()->hasGrantOption(flags, args...);
else
access_granted = getAccess()->isGranted(flags, args...);
if (trace_log)
LOG_TRACE(trace_log, "Access {}: {}{}", (access_granted ? "granted" : "denied"), (AccessRightsElement{flags, args...}.toString()),
(grant_option ? " WITH GRANT OPTION" : ""));
return access_granted;
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessFlags & flags) const
{
return isGrantedImpl2<grant_option>(flags);
}
template <bool grant_option, typename... Args>
bool ContextAccess::isGrantedImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
return isGrantedImpl2<grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessRightsElement & element) const
{
if (element.any_database)
return isGrantedImpl<grant_option>(element.access_flags);
else if (element.any_table)
return isGrantedImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table);
else
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!isGrantedImpl<grant_option>(element))
return false;
return true;
}
bool ContextAccess::isGranted(const AccessFlags & flags) const { return isGrantedImpl<false>(flags); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<false>(flags, database); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<false>(flags, database, table); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<false>(flags, database, table, column); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessRightsElement & element) const { return isGrantedImpl<false>(element); }
bool ContextAccess::isGranted(const AccessRightsElements & elements) const { return isGrantedImpl<false>(elements); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags) const { return isGrantedImpl<true>(flags); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<true>(flags, database); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<true>(flags, database, table); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<true>(flags, database, table, column); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool ContextAccess::hasGrantOption(const AccessRightsElement & element) const { return isGrantedImpl<true>(element); }
bool ContextAccess::hasGrantOption(const AccessRightsElements & elements) const { return isGrantedImpl<true>(elements); }
template <bool grant_option, typename... Args>
void ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &... args) const
{
if constexpr (grant_option)
{
if (hasGrantOption(flags, args...))
return;
}
else
{
if (isGranted(flags, args...))
return;
}
auto show_error = [&](const String & msg, int error_code)
{
throw Exception(user_name + ": " + msg, error_code);
};
std::lock_guard lock{mutex};
if (!user)
{
show_error("User has been dropped", ErrorCodes::UNKNOWN_USER);
}
if (grant_option && access->isGranted(flags, args...))
{
show_error(
"Not enough privileges. "
"The required privileges have been granted, but without grant option. "
"To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + " WITH GRANT OPTION",
ErrorCodes::ACCESS_DENIED);
}
if (params.readonly)
{
if (!access_without_readonly)
{
Params changed_params = params;
changed_params.readonly = 0;
access_without_readonly = manager->getContextAccess(changed_params);
}
if (access_without_readonly->isGranted(flags, args...))
{
if (params.interface == ClientInfo::Interface::HTTP && params.http_method == ClientInfo::HTTPMethod::GET)
show_error(
"Cannot execute query in readonly mode. "
"For queries over HTTP, method GET implies readonly. You should use method POST for modifying queries",
ErrorCodes::READONLY);
else
show_error("Cannot execute query in readonly mode", ErrorCodes::READONLY);
}
}
if (!params.allow_ddl)
{
if (!access_with_allow_ddl)
{
Params changed_params = params;
changed_params.allow_ddl = true;
access_with_allow_ddl = manager->getContextAccess(changed_params);
}
if (access_with_allow_ddl->isGranted(flags, args...))
{
show_error("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
}
}
if (!params.allow_introspection)
{
if (!access_with_allow_introspection)
{
Params changed_params = params;
changed_params.allow_introspection = true;
access_with_allow_introspection = manager->getContextAccess(changed_params);
}
if (access_with_allow_introspection->isGranted(flags, args...))
{
show_error("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
}
}
show_error(
"Not enough privileges. To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""),
ErrorCodes::ACCESS_DENIED);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessFlags & flags) const
{
checkAccessImpl2<grant_option>(flags);
}
template <bool grant_option, typename... Args>
void ContextAccess::checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
checkAccessImpl2<grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessRightsElement & element) const
{
if (element.any_database)
checkAccessImpl<grant_option>(element.access_flags);
else if (element.any_table)
checkAccessImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
checkAccessImpl<grant_option>(element.access_flags, element.database, element.table);
else
checkAccessImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
checkAccessImpl<grant_option>(element);
}
void ContextAccess::checkAccess(const AccessFlags & flags) const { checkAccessImpl<false>(flags); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<false>(flags, database); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<false>(flags, database, table); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<false>(flags, database, table, column); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<false>(flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<false>(flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessRightsElement & element) const { checkAccessImpl<false>(element); }
void ContextAccess::checkAccess(const AccessRightsElements & elements) const { checkAccessImpl<false>(elements); }
void ContextAccess::checkGrantOption(const AccessFlags & flags) const { checkAccessImpl<true>(flags); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<true>(flags, database); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<true>(flags, database, table); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<true>(flags, database, table, column); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<true>(flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<true>(flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessRightsElement & element) const { checkAccessImpl<true>(element); }
void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl<true>(elements); }
void ContextAccess::checkAdminOption(const UUID & role_id) const
{
if (isGranted(AccessType::ROLE_ADMIN))
return;
auto info = getRolesInfo();
if (info && info->enabled_roles_with_admin_option.count(role_id))
return;
if (!user)
throw Exception(user_name + ": User has been dropped", ErrorCodes::UNKNOWN_USER);
std::optional<String> role_name = manager->readName(role_id);
if (!role_name)
role_name = "ID {" + toString(role_id) + "}";
throw Exception(
getUserName() + ": Not enough privileges. To execute this query it's necessary to have the grant " + backQuoteIfNeed(*role_name)
+ " WITH ADMIN OPTION ",
ErrorCodes::ACCESS_DENIED);
}
}

View File

@ -6,7 +6,6 @@
#include <Core/UUID.h>
#include <ext/scope_guard.h>
#include <ext/shared_ptr_helper.h>
#include <boost/smart_ptr/atomic_shared_ptr.hpp>
#include <boost/container/flat_set.hpp>
#include <mutex>
@ -30,32 +29,34 @@ class IAST;
using ASTPtr = std::shared_ptr<IAST>;
struct ContextAccessParams
{
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
bool use_default_roles = false;
UInt64 readonly = 0;
bool allow_ddl = false;
bool allow_introspection = false;
String current_database;
ClientInfo::Interface interface = ClientInfo::Interface::TCP;
ClientInfo::HTTPMethod http_method = ClientInfo::HTTPMethod::UNKNOWN;
Poco::Net::IPAddress address;
String quota_key;
auto toTuple() const { return std::tie(user_id, current_roles, use_default_roles, readonly, allow_ddl, allow_introspection, current_database, interface, http_method, address, quota_key); }
friend bool operator ==(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator !=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs == rhs); }
friend bool operator <(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() < rhs.toTuple(); }
friend bool operator >(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return rhs < lhs; }
friend bool operator <=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(rhs < lhs); }
friend bool operator >=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs < rhs); }
};
class ContextAccess
{
public:
struct Params
{
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
bool use_default_roles = false;
UInt64 readonly = 0;
bool allow_ddl = false;
bool allow_introspection = false;
String current_database;
ClientInfo::Interface interface = ClientInfo::Interface::TCP;
ClientInfo::HTTPMethod http_method = ClientInfo::HTTPMethod::UNKNOWN;
Poco::Net::IPAddress address;
String quota_key;
auto toTuple() const { return std::tie(user_id, current_roles, use_default_roles, readonly, allow_ddl, allow_introspection, current_database, interface, http_method, address, quota_key); }
friend bool operator ==(const Params & lhs, const Params & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator !=(const Params & lhs, const Params & rhs) { return !(lhs == rhs); }
friend bool operator <(const Params & lhs, const Params & rhs) { return lhs.toTuple() < rhs.toTuple(); }
friend bool operator >(const Params & lhs, const Params & rhs) { return rhs < lhs; }
friend bool operator <=(const Params & lhs, const Params & rhs) { return !(rhs < lhs); }
friend bool operator >=(const Params & lhs, const Params & rhs) { return !(lhs < rhs); }
};
using Params = ContextAccessParams;
const Params & getParams() const { return params; }
/// Returns the current user. The function can return nullptr.
@ -90,16 +91,8 @@ public:
/// The function returns nullptr if there are no constraints.
std::shared_ptr<const SettingsConstraints> getSettingsConstraints() const;
/// Checks if a specified access is granted, and throws an exception if not.
/// Empty database means the current database.
void checkAccess(const AccessFlags & flags) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
void checkAccess(const AccessRightsElement & element) const;
void checkAccess(const AccessRightsElements & elements) const;
/// Returns the current access rights.
std::shared_ptr<const AccessRights> getAccess() const;
/// Checks if a specified access is granted.
bool isGranted(const AccessFlags & flags) const;
@ -111,17 +104,26 @@ public:
bool isGranted(const AccessRightsElement & element) const;
bool isGranted(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted, and logs a warning if not.
bool isGranted(Poco::Logger * log_, const AccessFlags & flags) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool isGranted(Poco::Logger * log_, const AccessRightsElement & element) const;
bool isGranted(Poco::Logger * log_, const AccessRightsElements & elements) const;
bool hasGrantOption(const AccessFlags & flags) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool hasGrantOption(const AccessRightsElement & element) const;
bool hasGrantOption(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted, and throws an exception if not.
/// Empty database means the current database.
void checkAccess(const AccessFlags & flags) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
void checkAccess(const AccessRightsElement & element) const;
void checkAccess(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted with grant option, and throws an exception if not.
void checkGrantOption(const AccessFlags & flags) const;
void checkGrantOption(const AccessFlags & flags, const std::string_view & database) const;
void checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
@ -146,24 +148,37 @@ private:
void setUser(const UserPtr & user_) const;
void setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> & roles_info_) const;
void setSettingsAndConstraints() const;
void setFinalAccess() const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags) const;
template <bool grant_option>
bool isGrantedImpl(const AccessFlags & flags) const;
template <int mode, bool grant_option, typename... Args>
bool checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <bool grant_option, typename... Args>
bool isGrantedImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessRightsElement & element) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElement & element) const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessRightsElements & elements) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElements & elements) const;
template <int mode, bool grant_option, typename... Args>
bool calculateResultAccessAndCheck(Poco::Logger * log_, const AccessFlags & flags, const Args &... args) const;
template <bool grant_option, typename... Args>
bool isGrantedImpl2(const AccessFlags & flags, const Args &... args) const;
boost::shared_ptr<const AccessRights> calculateResultAccess(bool grant_option) const;
boost::shared_ptr<const AccessRights> calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const;
template <bool grant_option>
void checkAccessImpl(const AccessFlags & flags) const;
template <bool grant_option, typename... Args>
void checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <bool grant_option>
void checkAccessImpl(const AccessRightsElement & element) const;
template <bool grant_option>
void checkAccessImpl(const AccessRightsElements & elements) const;
template <bool grant_option, typename... Args>
void checkAccessImpl2(const AccessFlags & flags, const Args &... args) const;
const AccessControlManager * manager = nullptr;
const Params params;
@ -174,10 +189,13 @@ private:
mutable std::shared_ptr<const EnabledRoles> enabled_roles;
mutable ext::scope_guard subscription_for_roles_changes;
mutable std::shared_ptr<const EnabledRolesInfo> roles_info;
mutable boost::atomic_shared_ptr<const AccessRights> result_access[7];
mutable std::shared_ptr<const AccessRights> access;
mutable std::shared_ptr<const EnabledRowPolicies> enabled_row_policies;
mutable std::shared_ptr<const EnabledQuota> enabled_quota;
mutable std::shared_ptr<const EnabledSettings> enabled_settings;
mutable std::shared_ptr<const ContextAccess> access_without_readonly;
mutable std::shared_ptr<const ContextAccess> access_with_allow_ddl;
mutable std::shared_ptr<const ContextAccess> access_with_allow_introspection;
mutable std::mutex mutex;
};

View File

@ -28,8 +28,7 @@ bool operator==(const EnabledRolesInfo & lhs, const EnabledRolesInfo & rhs)
{
return (lhs.current_roles == rhs.current_roles) && (lhs.enabled_roles == rhs.enabled_roles)
&& (lhs.enabled_roles_with_admin_option == rhs.enabled_roles_with_admin_option) && (lhs.names_of_roles == rhs.names_of_roles)
&& (lhs.access == rhs.access) && (lhs.access_with_grant_option == rhs.access_with_grant_option)
&& (lhs.settings_from_enabled_roles == rhs.settings_from_enabled_roles);
&& (lhs.access == rhs.access) && (lhs.settings_from_enabled_roles == rhs.settings_from_enabled_roles);
}
}

View File

@ -18,7 +18,6 @@ struct EnabledRolesInfo
boost::container::flat_set<UUID> enabled_roles_with_admin_option;
std::unordered_map<UUID, String> names_of_roles;
AccessRights access;
AccessRights access_with_grant_option;
SettingsProfileElements settings_from_enabled_roles;
Strings getCurrentRolesNames() const;

View File

@ -1,22 +0,0 @@
#include <Access/GrantedAccess.h>
namespace DB
{
GrantedAccess::GrantsAndPartialRevokes GrantedAccess::getGrantsAndPartialRevokes() const
{
GrantsAndPartialRevokes res;
res.grants_with_grant_option = access_with_grant_option.getGrants();
AccessRights access_without_gg = access;
access_without_gg.revoke(res.grants_with_grant_option);
auto gr = access_without_gg.getGrantsAndPartialRevokes();
res.grants = std::move(gr.grants);
res.revokes = std::move(gr.revokes);
AccessRights access_with_grant_options_without_r = access_with_grant_option;
access_with_grant_options_without_r.grant(res.revokes);
res.revokes_grant_option = access_with_grant_options_without_r.getPartialRevokes();
return res;
}
}

View File

@ -1,55 +0,0 @@
#pragma once
#include <Access/AccessRights.h>
namespace DB
{
/// Access rights as they are granted to a role or user.
/// Stores both the access rights themselves and the access rights with grant option.
struct GrantedAccess
{
AccessRights access;
AccessRights access_with_grant_option;
template <typename... Args>
void grant(const Args &... args)
{
access.grant(args...);
}
template <typename... Args>
void grantWithGrantOption(const Args &... args)
{
access.grant(args...);
access_with_grant_option.grant(args...);
}
template <typename... Args>
void revoke(const Args &... args)
{
access.revoke(args...);
access_with_grant_option.revoke(args...);
}
template <typename... Args>
void revokeGrantOption(const Args &... args)
{
access_with_grant_option.revoke(args...);
}
struct GrantsAndPartialRevokes
{
AccessRightsElements grants;
AccessRightsElements revokes;
AccessRightsElements grants_with_grant_option;
AccessRightsElements revokes_grant_option;
};
/// Retrieves the information about grants and partial revokes.
GrantsAndPartialRevokes getGrantsAndPartialRevokes() const;
friend bool operator ==(const GrantedAccess & left, const GrantedAccess & right) { return (left.access == right.access) && (left.access_with_grant_option == right.access_with_grant_option); }
friend bool operator !=(const GrantedAccess & left, const GrantedAccess & right) { return !(left == right); }
};
}

View File

@ -1,7 +1,7 @@
#pragma once
#include <Access/IAccessEntity.h>
#include <Access/GrantedAccess.h>
#include <Access/AccessRights.h>
#include <Access/GrantedRoles.h>
#include <Access/SettingsProfileElement.h>
@ -11,7 +11,7 @@ namespace DB
struct Role : public IAccessEntity
{
GrantedAccess access;
AccessRights access;
GrantedRoles granted_roles;
SettingsProfileElements settings;

View File

@ -43,8 +43,7 @@ namespace
roles_info.enabled_roles_with_admin_option.emplace(role_id);
roles_info.names_of_roles[role_id] = role->getName();
roles_info.access.merge(role->access.access);
roles_info.access_with_grant_option.merge(role->access.access_with_grant_option);
roles_info.access.merge(role->access);
roles_info.settings_from_enabled_roles.merge(role->settings);
for (const auto & granted_role : role->granted_roles.roles)

View File

@ -1,9 +1,9 @@
#pragma once
#include <Access/IAccessEntity.h>
#include <Access/AccessRights.h>
#include <Access/Authentication.h>
#include <Access/AllowedClientHosts.h>
#include <Access/GrantedAccess.h>
#include <Access/GrantedRoles.h>
#include <Access/RolesOrUsersSet.h>
#include <Access/SettingsProfileElement.h>
@ -17,7 +17,7 @@ struct User : public IAccessEntity
{
Authentication authentication;
AllowedClientHosts allowed_client_hosts = AllowedClientHosts::AnyHostTag{};
GrantedAccess access;
AccessRights access;
GrantedRoles granted_roles;
RolesOrUsersSet default_roles = RolesOrUsersSet::AllTag{};
SettingsProfileElements settings;

View File

@ -17,7 +17,6 @@ SRCS(
EnabledRolesInfo.cpp
EnabledRowPolicies.cpp
EnabledSettings.cpp
GrantedAccess.cpp
GrantedRoles.cpp
IAccessEntity.cpp
IAccessStorage.cpp

View File

@ -19,6 +19,7 @@
#include <Interpreters/AddDefaultDatabaseVisitor.h>
#include <Interpreters/Context.h>
#include <Access/AccessRightsElement.h>
#include <Access/ContextAccess.h>
#include <Common/DNSResolver.h>
#include <Common/Macros.h>
#include <common/getFQDNOrHostName.h>
@ -1278,7 +1279,7 @@ private:
};
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & context, AccessRightsElements && query_required_access)
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & context, AccessRightsElements && query_requires_access, bool query_requires_grant_option)
{
/// Remove FORMAT <fmt> and INTO OUTFILE <file> if exists
ASTPtr query_ptr = query_ptr_->clone();
@ -1323,10 +1324,10 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & cont
/// the local current database or a shard's default database.
bool need_replace_current_database
= (std::find_if(
query_required_access.begin(),
query_required_access.end(),
query_requires_access.begin(),
query_requires_access.end(),
[](const AccessRightsElement & elem) { return elem.isEmptyDatabase(); })
!= query_required_access.end());
!= query_requires_access.end());
if (need_replace_current_database)
{
@ -1355,29 +1356,31 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & cont
AddDefaultDatabaseVisitor visitor(current_database);
visitor.visitDDL(query_ptr);
query_required_access.replaceEmptyDatabase(current_database);
query_requires_access.replaceEmptyDatabase(current_database);
}
else
{
size_t old_num_elements = query_required_access.size();
for (size_t i = 0; i != old_num_elements; ++i)
for (size_t i = 0; i != query_requires_access.size();)
{
auto & element = query_required_access[i];
auto & element = query_requires_access[i];
if (element.isEmptyDatabase())
{
element.setDatabase(shard_default_databases[0]);
for (size_t j = 1; j != shard_default_databases.size(); ++j)
{
query_required_access.push_back(element);
query_required_access.back().setDatabase(shard_default_databases[j]);
}
query_requires_access.insert(query_requires_access.begin() + i + 1, shard_default_databases.size() - 1, element);
for (size_t j = 0; j != shard_default_databases.size(); ++j)
query_requires_access[i + j].replaceEmptyDatabase(shard_default_databases[j]);
i += shard_default_databases.size();
}
else
++i;
}
}
}
/// Check access rights, assume that all servers have the same users config
context.checkAccess(query_required_access);
if (query_requires_grant_option)
context.getAccess()->checkGrantOption(query_requires_access);
else
context.checkAccess(query_requires_access);
DDLLogEntry entry;
entry.hosts = std::move(hosts);
@ -1394,6 +1397,10 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & cont
return io;
}
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, const AccessRightsElements & query_requires_access, bool query_requires_grant_option)
{
return executeDDLQueryOnCluster(query_ptr, context, AccessRightsElements{query_requires_access}, query_requires_grant_option);
}
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & context)
{

View File

@ -29,8 +29,9 @@ struct DDLTask;
/// Pushes distributed DDL query to the queue
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, AccessRightsElements && query_required_access);
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context);
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, const AccessRightsElements & query_requires_access, bool query_requires_grant_option = false);
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, AccessRightsElements && query_requires_access, bool query_requires_grant_option = false);
class DDLWorker

View File

@ -16,7 +16,7 @@ namespace DB
namespace
{
template <typename T>
void updateFromQueryImpl(T & grantee, const ASTGrantQuery & query, const std::vector<UUID> & roles_from_query, const String & current_database)
void updateFromQueryImpl(T & grantee, const ASTGrantQuery & query, const std::vector<UUID> & roles_from_query)
{
using Kind = ASTGrantQuery::Kind;
if (!query.access_rights_elements.empty())
@ -24,16 +24,16 @@ namespace
if (query.kind == Kind::GRANT)
{
if (query.grant_option)
grantee.access.grantWithGrantOption(query.access_rights_elements, current_database);
grantee.access.grantWithGrantOption(query.access_rights_elements);
else
grantee.access.grant(query.access_rights_elements, current_database);
grantee.access.grant(query.access_rights_elements);
}
else
{
if (query.grant_option)
grantee.access.revokeGrantOption(query.access_rights_elements, current_database);
grantee.access.revokeGrantOption(query.access_rights_elements);
else
grantee.access.revoke(query.access_rights_elements, current_database);
grantee.access.revoke(query.access_rights_elements);
}
}
@ -67,9 +67,9 @@ namespace
BlockIO InterpreterGrantQuery::execute()
{
auto & query = query_ptr->as<ASTGrantQuery &>();
auto & access_control = context.getAccessControlManager();
query.replaceCurrentUserTagWithName(context.getUserName());
auto access = context.getAccess();
access->checkGrantOption(query.access_rights_elements);
auto & access_control = context.getAccessControlManager();
std::vector<UUID> roles_from_query;
if (query.roles)
@ -80,25 +80,24 @@ BlockIO InterpreterGrantQuery::execute()
}
if (!query.cluster.empty())
{
query.replaceCurrentUserTagWithName(context.getUserName());
return executeDDLQueryOnCluster(query_ptr, context);
}
return executeDDLQueryOnCluster(query_ptr, context, query.access_rights_elements, true);
query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
access->checkGrantOption(query.access_rights_elements);
std::vector<UUID> to_roles = RolesOrUsersSet{*query.to_roles, access_control, context.getUserID()}.getMatchingIDs(access_control);
String current_database = context.getCurrentDatabase();
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
{
auto clone = entity->clone();
if (auto user = typeid_cast<std::shared_ptr<User>>(clone))
{
updateFromQueryImpl(*user, query, roles_from_query, current_database);
updateFromQueryImpl(*user, query, roles_from_query);
return user;
}
else if (auto role = typeid_cast<std::shared_ptr<Role>>(clone))
{
updateFromQueryImpl(*role, query, roles_from_query, current_database);
updateFromQueryImpl(*role, query, roles_from_query);
return role;
}
else
@ -116,7 +115,7 @@ void InterpreterGrantQuery::updateUserFromQuery(User & user, const ASTGrantQuery
std::vector<UUID> roles_from_query;
if (query.roles)
roles_from_query = RolesOrUsersSet{*query.roles}.getMatchingIDs();
updateFromQueryImpl(user, query, roles_from_query, {});
updateFromQueryImpl(user, query, roles_from_query);
}
@ -125,7 +124,7 @@ void InterpreterGrantQuery::updateRoleFromQuery(Role & role, const ASTGrantQuery
std::vector<UUID> roles_from_query;
if (query.roles)
roles_from_query = RolesOrUsersSet{*query.roles}.getMatchingIDs();
updateFromQueryImpl(role, query, roles_from_query, {});
updateFromQueryImpl(role, query, roles_from_query);
}
}

View File

@ -84,19 +84,20 @@ static QueryDescriptors extractQueriesExceptMeAndCheckAccess(const Block & proce
const ColumnString & user_col = typeid_cast<const ColumnString &>(*processes_block.getByName("user").column);
const ClientInfo & my_client = context.getProcessListElement()->getClientInfo();
std::optional<bool> can_kill_query_started_by_another_user_cached;
auto can_kill_query_started_by_another_user = [&]() -> bool
bool access_denied = false;
std::optional<bool> is_kill_query_granted_value;
auto is_kill_query_granted = [&]() -> bool
{
if (!can_kill_query_started_by_another_user_cached)
if (!is_kill_query_granted_value)
{
can_kill_query_started_by_another_user_cached
= context.getAccess()->isGranted(&Poco::Logger::get("InterpreterKillQueryQuery"), AccessType::KILL_QUERY);
is_kill_query_granted_value = context.getAccess()->isGranted(AccessType::KILL_QUERY);
if (!*is_kill_query_granted_value)
access_denied = true;
}
return *can_kill_query_started_by_another_user_cached;
return *is_kill_query_granted_value;
};
String query_user;
bool access_denied = false;
for (size_t i = 0; i < num_processes; ++i)
{
@ -107,11 +108,8 @@ static QueryDescriptors extractQueriesExceptMeAndCheckAccess(const Block & proce
auto query_id = query_id_col.getDataAt(i).toString();
query_user = user_col.getDataAt(i).toString();
if ((my_client.current_user != query_user) && !can_kill_query_started_by_another_user())
{
access_denied = true;
if ((my_client.current_user != query_user) && !is_kill_query_granted())
continue;
}
res.emplace_back(std::move(query_id), query_user, i, false);
}
@ -269,7 +267,7 @@ BlockIO InterpreterKillQueryQuery::execute()
ParserAlterCommand parser;
auto command_ast = parseQuery(parser, command_col.getDataAt(i).toString(), 0, context.getSettingsRef().max_parser_depth);
required_access_rights = InterpreterAlterQuery::getRequiredAccessForCommand(command_ast->as<const ASTAlterCommand &>(), table_id.database_name, table_id.table_name);
if (!access->isGranted(&Poco::Logger::get("InterpreterKillQueryQuery"), required_access_rights))
if (!access->isGranted(required_access_rights))
{
access_denied = true;
continue;

View File

@ -35,44 +35,33 @@ namespace
std::shared_ptr<ASTRolesOrUsersSet> to_roles = std::make_shared<ASTRolesOrUsersSet>();
to_roles->names.push_back(grantee.getName());
auto grants_and_partial_revokes = grantee.access.getGrantsAndPartialRevokes();
std::shared_ptr<ASTGrantQuery> current_query = nullptr;
for (bool grant_option : {false, true})
auto elements = grantee.access.getElements();
for (const auto & element : elements)
{
using Kind = ASTGrantQuery::Kind;
for (Kind kind : {Kind::GRANT, Kind::REVOKE})
if (current_query)
{
AccessRightsElements * elements = nullptr;
if (grant_option)
elements = (kind == Kind::GRANT) ? &grants_and_partial_revokes.grants_with_grant_option : &grants_and_partial_revokes.revokes_grant_option;
else
elements = (kind == Kind::GRANT) ? &grants_and_partial_revokes.grants : &grants_and_partial_revokes.revokes;
elements->normalize();
std::shared_ptr<ASTGrantQuery> grant_query = nullptr;
for (size_t i = 0; i != elements->size(); ++i)
{
const auto & element = (*elements)[i];
bool prev_element_on_same_db_and_table = false;
if (grant_query)
{
const auto & prev_element = grant_query->access_rights_elements.back();
if ((element.database == prev_element.database) && (element.any_database == prev_element.any_database)
&& (element.table == prev_element.table) && (element.any_table == prev_element.any_table))
prev_element_on_same_db_and_table = true;
}
if (!prev_element_on_same_db_and_table)
{
grant_query = std::make_shared<ASTGrantQuery>();
grant_query->kind = kind;
grant_query->attach = attach_mode;
grant_query->grant_option = grant_option;
grant_query->to_roles = to_roles;
res.push_back(grant_query);
}
grant_query->access_rights_elements.emplace_back(std::move(element));
}
const auto & prev_element = current_query->access_rights_elements.back();
bool continue_using_current_query = (element.database == prev_element.database)
&& (element.any_database == prev_element.any_database) && (element.table == prev_element.table)
&& (element.any_table == prev_element.any_table) && (element.grant_option == current_query->grant_option)
&& (element.kind == current_query->kind);
if (!continue_using_current_query)
current_query = nullptr;
}
if (!current_query)
{
current_query = std::make_shared<ASTGrantQuery>();
current_query->kind = element.kind;
current_query->attach = attach_mode;
current_query->grant_option = element.grant_option;
current_query->to_roles = to_roles;
res.push_back(current_query);
}
current_query->access_rights_elements.emplace_back(std::move(element));
}
auto grants_roles = grantee.granted_roles.getGrants();

View File

@ -152,8 +152,16 @@ void InterpreterSystemQuery::startStopAction(StorageActionBlockType action_type,
if (!table)
continue;
if (!access->isGranted(log, getRequiredAccessType(action_type), elem.first, iterator->name()))
if (!access->isGranted(getRequiredAccessType(action_type), elem.first, iterator->name()))
{
LOG_INFO(
log,
"Access {} denied, skipping {}.{}",
toString(getRequiredAccessType(action_type)),
elem.first,
iterator->name());
continue;
}
if (start)
manager->remove(table, action_type);

View File

@ -218,7 +218,7 @@ void runOneTest(const TestDescriptor & test_descriptor)
try
{
res = acl_manager.read<DB::User>(entry.user_name)->access.access.isGranted(DB::AccessType::ALL, entry.database_name);
res = acl_manager.read<DB::User>(entry.user_name)->access.isGranted(DB::AccessType::ALL, entry.database_name);
}
catch (const Poco::Exception &)
{

View File

@ -133,6 +133,12 @@ void ASTGrantQuery::formatImpl(const FormatSettings & settings, FormatState &, F
}
void ASTGrantQuery::replaceEmptyDatabaseWithCurrent(const String & current_database)
{
access_rights_elements.replaceEmptyDatabase(current_database);
}
void ASTGrantQuery::replaceCurrentUserTagWithName(const String & current_user_name) const
{
if (to_roles)

View File

@ -19,11 +19,7 @@ class ASTRolesOrUsersSet;
class ASTGrantQuery : public IAST, public ASTQueryWithOnCluster
{
public:
enum class Kind
{
GRANT,
REVOKE,
};
using Kind = AccessRightsElementWithOptions::Kind;
Kind kind = Kind::GRANT;
bool attach = false;
AccessRightsElements access_rights_elements;
@ -35,6 +31,7 @@ public:
String getID(char) const override;
ASTPtr clone() const override;
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
void replaceEmptyDatabaseWithCurrent(const String & current_database);
void replaceCurrentUserTagWithName(const String & current_user_name) const;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTGrantQuery>(clone()); }
};

View File

@ -8,6 +8,7 @@
#include <Columns/ColumnNullable.h>
#include <Columns/ColumnsNumber.h>
#include <Access/AccessControlManager.h>
#include <Access/AccessRightsElement.h>
#include <Access/Role.h>
#include <Access/User.h>
#include <Interpreters/Context.h>
@ -17,7 +18,7 @@
namespace DB
{
using EntityType = IAccessEntity::Type;
using Kind = AccessRightsElementWithOptions::Kind;
NamesAndTypesList StorageSystemGrants::getNamesAndTypes()
{
@ -63,7 +64,7 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
const String * database,
const String * table,
const String * column,
bool is_partial_revoke,
Kind kind,
bool grant_option)
{
if (grantee_type == EntityType::USER)
@ -118,15 +119,13 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
column_column_null_map.push_back(true);
}
column_is_partial_revoke.push_back(is_partial_revoke);
column_is_partial_revoke.push_back(kind == Kind::REVOKE);
column_grant_option.push_back(grant_option);
};
auto add_rows = [&](const String & grantee_name,
IAccessEntity::Type grantee_type,
const AccessRightsElements & elements,
bool is_partial_revoke,
bool grant_option)
const AccessRightsElementsWithOptions & elements)
{
for (const auto & element : elements)
{
@ -140,13 +139,13 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
if (element.any_column)
{
for (const auto & access_type : access_types)
add_row(grantee_name, grantee_type, access_type, database, table, nullptr, is_partial_revoke, grant_option);
add_row(grantee_name, grantee_type, access_type, database, table, nullptr, element.kind, element.grant_option);
}
else
{
for (const auto & access_type : access_types)
for (const auto & column : element.columns)
add_row(grantee_name, grantee_type, access_type, database, table, &column, is_partial_revoke, grant_option);
add_row(grantee_name, grantee_type, access_type, database, table, &column, element.kind, element.grant_option);
}
}
};
@ -157,7 +156,7 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
if (!entity)
continue;
const GrantedAccess * access = nullptr;
const AccessRights * access = nullptr;
if (auto role = typeid_cast<RolePtr>(entity))
access = &role->access;
else if (auto user = typeid_cast<UserPtr>(entity))
@ -167,13 +166,8 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
const String & grantee_name = entity->getName();
const auto grantee_type = entity->getType();
auto grants_and_revokes = access->access.getGrantsAndPartialRevokes();
auto grants_and_revokes_with_grant_option = access->access_with_grant_option.getGrantsAndPartialRevokes();
add_rows(grantee_name, grantee_type, grants_and_revokes.grants, /* is_partial_revoke = */ false, /* grant_option = */ false);
add_rows(grantee_name, grantee_type, grants_and_revokes.revokes, /* is_partial_revoke = */ true, /* grant_option = */ false);
add_rows(grantee_name, grantee_type, grants_and_revokes_with_grant_option.grants, /* is_partial_revoke = */ false, /* grant_option = */ true);
add_rows(grantee_name, grantee_type, grants_and_revokes_with_grant_option.revokes, /* is_partial_revoke = */ true, /* grant_option = */ true);
auto elements = access->getElements();
add_rows(grantee_name, grantee_type, elements);
}
}

View File

@ -100,7 +100,6 @@ def test_introspection():
assert instance.query("SELECT * from system.grants WHERE user_name IN ('A', 'B') ORDER BY user_name, access_type, grant_option") ==\
TSV([[ "A", "\N", "SELECT", "test", "table", "\N", 0, 0 ],
[ "B", "\N", "CREATE", "\N", "\N", "\N", 0, 0 ],
[ "B", "\N", "CREATE", "\N", "\N", "\N", 0, 1 ]])

View File

@ -140,7 +140,6 @@ def test_introspection():
assert instance.query("SELECT * from system.grants WHERE user_name IN ('A', 'B') OR role_name IN ('R1', 'R2') ORDER BY user_name, role_name, access_type, grant_option") ==\
TSV([[ "A", "\N", "SELECT", "test", "table", "\N", 0, 0 ],
[ "B", "\N", "CREATE", "\N", "\N", "\N", 0, 0 ],
[ "B", "\N", "CREATE", "\N", "\N", "\N", 0, 1 ],
[ "\N", "R2", "SELECT", "test", "table", "\N", 0, 0 ],
[ "\N", "R2", "SELECT", "test", "table", "x", 1, 0 ]])

View File

@ -1,11 +1,11 @@
CREATE USER test_user_01073
A
B
GRANT INSERT, ALTER DELETE ON *.* TO test_user_01073
GRANT SELECT ON db1.* TO test_user_01073
GRANT SELECT ON db2.table TO test_user_01073
GRANT SELECT(col1) ON db3.table TO test_user_01073
GRANT SELECT(col1, col2) ON db4.table TO test_user_01073
GRANT INSERT, ALTER DELETE ON *.* TO test_user_01073
C
GRANT SELECT(col1) ON db4.table TO test_user_01073
GRANT ALTER DELETE ON *.* TO test_user_01073
GRANT SELECT(col1) ON db4.table TO test_user_01073

View File

@ -1,2 +1,61 @@
--simple 1
GRANT SELECT ON *.* TO test_user_01074
REVOKE SELECT ON db.* FROM test_user_01074
--cleanup
--simple 2
GRANT SELECT ON db.* TO test_user_01074
REVOKE SELECT ON db.table FROM test_user_01074
--cleanup
--simple 3
GRANT SELECT ON db.table TO test_user_01074
REVOKE SELECT(col1) ON db.table FROM test_user_01074
--cleanup
--complex 1
GRANT SELECT ON *.* TO test_user_01074
REVOKE SELECT(col1, col2) ON db.table FROM test_user_01074
--cleanup
--complex 2
GRANT SELECT ON *.* TO test_user_01074
REVOKE SELECT ON db.* FROM test_user_01074
GRANT SELECT ON db.table TO test_user_01074
REVOKE SELECT(col1) ON db.table FROM test_user_01074
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
user_name  ┃ role_name ┃ access_type ┃ database ┃ table ┃ column ┃ is_partial_revoke ┃ grant_option ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ 0 │ 0 │
├─────────────────┼───────────┼─────────────┼──────────┼───────┼────────┼───────────────────┼──────────────┤
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ db │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ 1 │ 0 │
├─────────────────┼───────────┼─────────────┼──────────┼───────┼────────┼───────────────────┼──────────────┤
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ db │ table │ ᴺᵁᴸᴸ │ 0 │ 0 │
├─────────────────┼───────────┼─────────────┼──────────┼───────┼────────┼───────────────────┼──────────────┤
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ db │ table │ col1 │ 1 │ 0 │
└─────────────────┴───────────┴─────────────┴──────────┴───────┴────────┴───────────────────┴──────────────┘
--cleanup
--revoke 1
GRANT SELECT ON *.* TO test_user_01074
REVOKE SELECT ON db.* FROM test_user_01074
--cleanup
--revoke 2
GRANT SELECT ON *.* TO test_user_01074
--cleanup
--grant option 1
GRANT SELECT ON *.* TO test_user_01074 WITH GRANT OPTION
REVOKE GRANT OPTION FOR SELECT(col1) ON db.table FROM test_user_01074
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
user_name  ┃ role_name ┃ access_type ┃ database ┃ table ┃ column ┃ is_partial_revoke ┃ grant_option ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ 0 │ 1 │
├─────────────────┼───────────┼─────────────┼──────────┼───────┼────────┼───────────────────┼──────────────┤
│ test_user_01074 │ ᴺᵁᴸᴸ │ SELECT │ db │ table │ col1 │ 1 │ 1 │
└─────────────────┴───────────┴─────────────┴──────────┴───────┴────────┴───────────────────┴──────────────┘
--cleanup
--grant option 2
GRANT SELECT ON *.* TO test_user_01074 WITH GRANT OPTION
REVOKE SELECT(col1) ON db.table FROM test_user_01074
--cleanup
--grant option 3
GRANT SELECT ON *.* TO test_user_01074
--cleanup
--grant option 4
GRANT SELECT ON *.* TO test_user_01074
GRANT SELECT ON db.* TO test_user_01074 WITH GRANT OPTION

View File

@ -1,8 +1,106 @@
DROP USER IF EXISTS test_user_01074;
CREATE USER test_user_01074;
SELECT '--simple 1';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT ON db.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--simple 2';
GRANT SELECT ON db.* TO test_user_01074;
REVOKE SELECT ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--simple 3';
GRANT SELECT ON db.table TO test_user_01074;
REVOKE SELECT(col1) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--complex 1';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT(col1, col2) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--complex 2';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT ON db.* FROM test_user_01074;
GRANT SELECT ON db.table TO test_user_01074;
REVOKE SELECT(col1) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT * FROM system.grants WHERE user_name = 'test_user_01074' format Pretty;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--revoke 1';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT ON db.table FROM test_user_01074;
REVOKE SELECT ON db.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--revoke 2';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT ON db.table FROM test_user_01074;
GRANT SELECT ON db.* TO test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--grant option 1';
GRANT SELECT ON *.* TO test_user_01074 WITH GRANT OPTION;
REVOKE GRANT OPTION FOR SELECT(col1) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT * FROM system.grants WHERE user_name = 'test_user_01074' format Pretty;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--grant option 2';
GRANT SELECT ON *.* TO test_user_01074 WITH GRANT OPTION;
REVOKE SELECT(col1) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--grant option 3';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE GRANT OPTION FOR SELECT(col1) ON db.table FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--cleanup';
REVOKE SELECT ON *.* FROM test_user_01074;
SHOW GRANTS FOR test_user_01074;
SELECT '--grant option 4';
GRANT SELECT ON *.* TO test_user_01074;
REVOKE SELECT ON db.table FROM test_user_01074;
GRANT SELECT ON db.* TO test_user_01074 WITH GRANT OPTION;
SHOW GRANTS FOR test_user_01074;
DROP USER test_user_01074;