Refactoring of getting information about access rights.

This commit is contained in:
Vitaly Baranov 2020-04-21 01:07:00 +03:00
parent 88039e8371
commit 66e348a93f
27 changed files with 584 additions and 260 deletions

View File

@ -61,6 +61,10 @@ public:
friend bool operator ==(const AccessFlags & left, const AccessFlags & right) { return left.flags == right.flags; }
friend bool operator !=(const AccessFlags & left, const AccessFlags & right) { return !(left == right); }
friend bool operator <(const AccessFlags & left, const AccessFlags & right) { return memcmp(&left.flags, &right.flags, sizeof(Flags)) < 0; }
friend bool operator >(const AccessFlags & left, const AccessFlags & right) { return right < left; }
friend bool operator <=(const AccessFlags & left, const AccessFlags & right) { return !(right < left); }
friend bool operator >=(const AccessFlags & left, const AccessFlags & right) { return !(left < right); }
void clear() { flags.reset(); }

View File

@ -606,69 +606,102 @@ void AccessRights::revoke(const AccessRightsElements & elements, std::string_vie
}
AccessRights::Elements AccessRights::getElements() const
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
{
if (!root)
return {};
Elements res;
return;
auto global_access = root->access;
if (global_access)
res.grants.push_back({global_access});
if (out_grants && global_access)
out_grants->push_back({global_access});
if (root->children)
{
for (const auto & [db_name, db_node] : *root->children)
{
auto db_grants = db_node.access - global_access;
auto db_partial_revokes = global_access - db_node.access;
if (db_partial_revokes)
res.partial_revokes.push_back({db_partial_revokes, db_name});
if (db_grants)
res.grants.push_back({db_grants, db_name});
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)
{
auto table_grants = table_node.access - db_node.access;
auto table_partial_revokes = db_node.access - table_node.access;
if (table_partial_revokes)
res.partial_revokes.push_back({table_partial_revokes, db_name, table_name});
if (table_grants)
res.grants.push_back({table_grants, db_name, table_name});
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)
{
auto column_grants = column_node.access - table_node.access;
auto column_partial_revokes = table_node.access - column_node.access;
if (column_partial_revokes)
res.partial_revokes.push_back({column_partial_revokes, db_name, table_name, column_name});
if (column_grants)
res.grants.push_back({column_grants, db_name, table_name, column_name});
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 res;
}
String AccessRights::toString() const
{
auto elements = getElements();
String res;
if (!elements.grants.empty())
auto gr = getGrantsAndPartialRevokes();
if (!gr.grants.empty())
{
res += "GRANT ";
res += elements.grants.toString();
res += gr.grants.toString();
}
if (!elements.partial_revokes.empty())
if (!gr.revokes.empty())
{
if (!res.empty())
res += ", ";
res += "REVOKE ";
res += elements.partial_revokes.toString();
res += gr.revokes.toString();
}
if (res.empty())
res = "GRANT USAGE ON *.*";

View File

@ -49,12 +49,14 @@ public:
void revoke(const AccessRightsElements & elements, std::string_view current_database = {});
/// Returns the information about all the access granted.
struct Elements
struct GrantsAndPartialRevokes
{
AccessRightsElements grants;
AccessRightsElements partial_revokes;
AccessRightsElements revokes;
};
Elements getElements() const;
AccessRightsElements getGrants() const;
AccessRightsElements getPartialRevokes() const;
GrantsAndPartialRevokes getGrantsAndPartialRevokes() const;
/// Returns the information about all the access granted as a string.
String toString() const;
@ -92,6 +94,8 @@ private:
template <typename... Args>
AccessFlags getAccessImpl(const Args &... args) const;
void getGrantsAndPartialRevokesImpl(AccessRightsElements * grants, AccessRightsElements * partial_revokes) const;
void logTree() const;
struct Node;

View File

@ -1,10 +1,124 @@
#include <Access/AccessRightsElement.h>
#include <Dictionaries/IDictionary.h>
#include <Common/quoteString.h>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/algorithm/unique.hpp>
#include <boost/range/algorithm_ext/is_sorted.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
namespace DB
{
namespace
{
size_t groupElements(AccessRightsElements & elements, size_t start)
{
auto & start_element = elements[start];
auto it = std::find_if(elements.begin() + start + 1, elements.end(),
[&](const AccessRightsElement & element)
{
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;
}
/// 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)
{
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;
}
}
}
return end;
}
/// Tries to combine elements to decrease their number.
void groupElements(AccessRightsElements & elements)
{
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);
}
/// Removes unnecessary elements, sorts elements and makes them unique.
void sortElementsAndMakeUnique(AccessRightsElements & elements)
{
/// Remove empty elements.
boost::range::remove_erase_if(elements, [](const AccessRightsElement & element)
{
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());
}
/// Sort elements themselves.
boost::range::sort(elements);
elements.erase(std::unique(elements.begin(), elements.end()), elements.end());
}
}
void AccessRightsElement::setDatabase(const String & new_database)
{
database = new_database;
@ -26,10 +140,29 @@ bool AccessRightsElement::isEmptyDatabase() const
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;
}
String AccessRightsElement::toStringWithoutON() 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() ? "(" : ", ";
@ -38,25 +171,17 @@ String AccessRightsElement::toString() const
columns_in_parentheses += ")";
}
auto keywords = access_flags.toKeywords();
if (keywords.empty())
return "USAGE";
String msg;
for (const std::string_view & keyword : access_flags.toKeywords())
for (const std::string_view & keyword : keywords)
{
if (!msg.empty())
msg += ", ";
msg += String{keyword} + columns_in_parentheses;
}
msg += " ON ";
if (any_database)
msg += "*.";
else if (!database.empty() && (database != IDictionary::NO_DATABASE_TAG))
msg += backQuoteIfNeed(database) + ".";
if (any_table)
msg += "*";
else
msg += backQuoteIfNeed(table);
return msg;
}
@ -68,19 +193,41 @@ void AccessRightsElements::replaceEmptyDatabase(const String & new_database)
}
String AccessRightsElements::toString() const
String AccessRightsElements::toString()
{
String res;
bool need_comma = false;
for (const auto & element : *this)
{
if (std::exchange(need_comma, true))
res += ", ";
res += element.toString();
}
normalize();
if (res.empty())
res = "USAGE ON *.*";
return res;
if (empty())
return "USAGE ON *.*";
String msg;
bool need_comma = false;
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 (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 (next_element_on_same_db_and_table)
msg += element.toStringWithoutON();
else
msg += element.toString();
}
return msg;
}
void AccessRightsElements::normalize()
{
groupElements(*this);
sortElementsAndMakeUnique(*this);
}
}

View File

@ -71,6 +71,14 @@ struct AccessRightsElement
{
}
auto toTuple() const { return std::tie(access_flags, database, any_database, table, any_table, columns, any_column); }
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(); }
/// Sets the database.
void setDatabase(const String & new_database);
@ -82,6 +90,7 @@ struct AccessRightsElement
/// 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;
};
@ -94,7 +103,11 @@ public:
/// 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 toString() const { return AccessRightsElements(*this).toString(); }
String toString();
/// Reorder and group elements to show them in more readable form.
void normalize();
};
}

View File

@ -167,50 +167,48 @@ enum class AccessType
#undef DECLARE_ACCESS_TYPE_ENUM_CONST
};
std::string_view toString(AccessType type);
namespace impl
{
template <typename = void>
class AccessTypeToKeywordConverter
class AccessTypeToStringConverter
{
public:
static const AccessTypeToKeywordConverter & instance()
static const AccessTypeToStringConverter & instance()
{
static const AccessTypeToKeywordConverter res;
static const AccessTypeToStringConverter res;
return res;
}
std::string_view convert(AccessType type) const
{
return access_type_to_keyword_mapping[static_cast<size_t>(type)];
return access_type_to_string_mapping[static_cast<size_t>(type)];
}
private:
AccessTypeToKeywordConverter()
AccessTypeToStringConverter()
{
#define INSERT_ACCESS_TYPE_KEYWORD_PAIR_TO_MAPPING(name, aliases, node_type, parent_group_name) \
insertToMapping(AccessType::name, #name);
#define ACCESS_TYPE_TO_STRING_CONVERTER_ADD_TO_MAPPING(name, aliases, node_type, parent_group_name) \
addToMapping(AccessType::name, #name);
APPLY_FOR_ACCESS_TYPES(INSERT_ACCESS_TYPE_KEYWORD_PAIR_TO_MAPPING)
APPLY_FOR_ACCESS_TYPES(ACCESS_TYPE_TO_STRING_CONVERTER_ADD_TO_MAPPING)
#undef INSERT_ACCESS_TYPE_KEYWORD_PAIR_TO_MAPPING
#undef ACCESS_TYPE_TO_STRING_CONVERTER_ADD_TO_MAPPING
}
void insertToMapping(AccessType type, const std::string_view & str)
void addToMapping(AccessType type, const std::string_view & str)
{
String str2{str};
boost::replace_all(str2, "_", " ");
size_t index = static_cast<size_t>(type);
access_type_to_keyword_mapping.resize(std::max(index + 1, access_type_to_keyword_mapping.size()));
access_type_to_keyword_mapping[index] = str2;
access_type_to_string_mapping.resize(std::max(index + 1, access_type_to_string_mapping.size()));
access_type_to_string_mapping[index] = str2;
}
Strings access_type_to_keyword_mapping;
Strings access_type_to_string_mapping;
};
}
inline std::string_view toKeyword(AccessType type) { return impl::AccessTypeToKeywordConverter<>::instance().convert(type); }
inline std::string_view toString(AccessType type) { return impl::AccessTypeToStringConverter<>::instance().convert(type); }
}

View File

@ -134,12 +134,12 @@ void ContextAccess::setUser(const UserPtr & user_) const
std::vector<UUID> current_roles, current_roles_with_admin_option;
if (params.use_default_roles)
{
for (const UUID & id : user->granted_roles)
for (const UUID & id : user->granted_roles.roles)
{
if (user->default_roles.match(id))
current_roles.push_back(id);
}
boost::range::set_intersection(current_roles, user->granted_roles_with_admin_option,
boost::range::set_intersection(current_roles, user->granted_roles.roles_with_admin_option,
std::back_inserter(current_roles_with_admin_option));
}
else
@ -147,9 +147,9 @@ void ContextAccess::setUser(const UserPtr & user_) const
current_roles.reserve(params.current_roles.size());
for (const auto & id : params.current_roles)
{
if (user->granted_roles.count(id))
if (user->granted_roles.roles.contains(id))
current_roles.push_back(id);
if (user->granted_roles_with_admin_option.count(id))
if (user->granted_roles.roles_with_admin_option.contains(id))
current_roles_with_admin_option.push_back(id);
}
}
@ -397,13 +397,13 @@ boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool
if (grant_option)
{
*merged_access = user->access_with_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;
*merged_access = user->access.access;
if (roles_info)
merged_access->merge(roles_info->access);
}

View File

@ -1,3 +1,4 @@
#include <Access/ExtendedRoleSet.h>
#include <Access/AccessControlManager.h>
#include <Access/User.h>
@ -43,12 +44,6 @@ ExtendedRoleSet::ExtendedRoleSet(const std::vector<UUID> & ids_)
}
ExtendedRoleSet::ExtendedRoleSet(const boost::container::flat_set<UUID> & ids_)
{
add(ids_);
}
ExtendedRoleSet::ExtendedRoleSet(const ASTExtendedRoleSet & ast)
{
init(ast, nullptr);
@ -126,6 +121,7 @@ std::shared_ptr<ASTExtendedRoleSet> ExtendedRoleSet::toAST() const
ast->names.reserve(ids.size());
for (const UUID & id : ids)
ast->names.emplace_back(::DB::toString(id));
boost::range::sort(ast->names);
}
if (!except_ids.empty())
@ -133,6 +129,7 @@ std::shared_ptr<ASTExtendedRoleSet> ExtendedRoleSet::toAST() const
ast->except_names.reserve(except_ids.size());
for (const UUID & except_id : except_ids)
ast->except_names.emplace_back(::DB::toString(except_id));
boost::range::sort(ast->except_names);
}
return ast;
@ -244,13 +241,6 @@ void ExtendedRoleSet::add(const std::vector<UUID> & ids_)
}
void ExtendedRoleSet::add(const boost::container::flat_set<UUID> & ids_)
{
for (const auto & id : ids_)
add(id);
}
bool ExtendedRoleSet::match(const UUID & id) const
{
return (all || ids.count(id)) && !except_ids.count(id);

View File

@ -28,7 +28,6 @@ struct ExtendedRoleSet
ExtendedRoleSet(const UUID & id);
ExtendedRoleSet(const std::vector<UUID> & ids_);
ExtendedRoleSet(const boost::container::flat_set<UUID> & ids_);
/// The constructor from AST requires the AccessControlManager if `ast.id_mode == false`.
ExtendedRoleSet(const ASTExtendedRoleSet & ast);
@ -48,7 +47,6 @@ struct ExtendedRoleSet
void clear();
void add(const UUID & id);
void add(const std::vector<UUID> & ids_);
void add(const boost::container::flat_set<UUID> & ids_);
/// Checks if a specified ID matches this ExtendedRoleSet.
bool match(const UUID & id) const;

View File

@ -0,0 +1,22 @@
#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

@ -0,0 +1,55 @@
#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

@ -0,0 +1,64 @@
#include <Access/GrantedRoles.h>
#include <boost/range/algorithm/set_algorithm.hpp>
namespace DB
{
void GrantedRoles::grant(const UUID & role)
{
roles.insert(role);
}
void GrantedRoles::grant(const std::vector<UUID> & roles_)
{
for (const UUID & role : roles_)
grant(role);
}
void GrantedRoles::grantWithAdminOption(const UUID & role)
{
roles.insert(role);
roles_with_admin_option.insert(role);
}
void GrantedRoles::grantWithAdminOption(const std::vector<UUID> & roles_)
{
for (const UUID & role : roles_)
grantWithAdminOption(role);
}
void GrantedRoles::revoke(const UUID & role)
{
roles.erase(role);
roles_with_admin_option.erase(role);
}
void GrantedRoles::revoke(const std::vector<UUID> & roles_)
{
for (const UUID & role : roles_)
revoke(role);
}
void GrantedRoles::revokeAdminOption(const UUID & role)
{
roles_with_admin_option.erase(role);
}
void GrantedRoles::revokeAdminOption(const std::vector<UUID> & roles_)
{
for (const UUID & role : roles_)
revokeAdminOption(role);
}
GrantedRoles::Grants GrantedRoles::getGrants() const
{
Grants res;
res.grants_with_admin_option.insert(res.grants_with_admin_option.end(), roles_with_admin_option.begin(), roles_with_admin_option.end());
res.grants.reserve(roles.size() - roles_with_admin_option.size());
boost::range::set_difference(roles, roles_with_admin_option, std::back_inserter(res.grants));
return res;
}
}

39
src/Access/GrantedRoles.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <Core/UUID.h>
#include <boost/container/flat_set.hpp>
#include <vector>
namespace DB
{
/// Roles when they are granted to a role or user.
/// Stores both the roles themselves and the roles with admin option.
struct GrantedRoles
{
boost::container::flat_set<UUID> roles;
boost::container::flat_set<UUID> roles_with_admin_option;
void grant(const UUID & role);
void grant(const std::vector<UUID> & roles_);
void grantWithAdminOption(const UUID & role);
void grantWithAdminOption(const std::vector<UUID> & roles_);
void revoke(const UUID & role);
void revoke(const std::vector<UUID> & roles_);
void revokeAdminOption(const UUID & role);
void revokeAdminOption(const std::vector<UUID> & roles_);
struct Grants
{
std::vector<UUID> grants;
std::vector<UUID> grants_with_admin_option;
};
/// Retrieves the information about grants.
Grants getGrants() const;
friend bool operator ==(const GrantedRoles & left, const GrantedRoles & right) { return (left.roles == right.roles) && (left.roles_with_admin_option == right.roles_with_admin_option); }
friend bool operator !=(const GrantedRoles & left, const GrantedRoles & right) { return !(left == right); }
};
}

View File

@ -9,9 +9,6 @@ bool Role::equal(const IAccessEntity & other) const
if (!IAccessEntity::equal(other))
return false;
const auto & other_role = typeid_cast<const Role &>(other);
return (access == other_role.access) && (access_with_grant_option == other_role.access_with_grant_option)
&& (granted_roles == other_role.granted_roles) && (granted_roles_with_admin_option == other_role.granted_roles_with_admin_option)
&& (settings == other_role.settings);
return (access == other_role.access) && (granted_roles == other_role.granted_roles) && (settings == other_role.settings);
}
}

View File

@ -1,10 +1,9 @@
#pragma once
#include <Access/IAccessEntity.h>
#include <Access/AccessRights.h>
#include <Access/GrantedAccess.h>
#include <Access/GrantedRoles.h>
#include <Access/SettingsProfileElement.h>
#include <Core/UUID.h>
#include <boost/container/flat_set.hpp>
namespace DB
@ -12,10 +11,8 @@ namespace DB
struct Role : public IAccessEntity
{
AccessRights access;
AccessRights access_with_grant_option;
boost::container::flat_set<UUID> granted_roles;
boost::container::flat_set<UUID> granted_roles_with_admin_option;
GrantedAccess access;
GrantedRoles granted_roles;
SettingsProfileElements settings;
bool equal(const IAccessEntity & other) const override;

View File

@ -37,10 +37,10 @@ namespace
if (!role)
return;
for (const auto & granted_role : role->granted_roles)
for (const auto & granted_role : role->granted_roles.roles)
collectRoles(collected_roles, get_role_function, granted_role, false, false);
for (const auto & granted_role : role->granted_roles_with_admin_option)
for (const auto & granted_role : role->granted_roles.roles_with_admin_option)
collectRoles(collected_roles, get_role_function, granted_role, false, true);
}
@ -59,8 +59,8 @@ namespace
if (collect_info.with_admin_option)
new_info->enabled_roles_with_admin_option.emplace_back(role_id);
new_info->names_of_roles[role_id] = role->getName();
new_info->access.merge(role->access);
new_info->access_with_grant_option.merge(role->access_with_grant_option);
new_info->access.merge(role->access.access);
new_info->access_with_grant_option.merge(role->access.access_with_grant_option);
new_info->settings_from_enabled_roles.merge(role->settings);
}
return new_info;

View File

@ -10,9 +10,7 @@ bool User::equal(const IAccessEntity & other) const
return false;
const auto & other_user = typeid_cast<const User &>(other);
return (authentication == other_user.authentication) && (allowed_client_hosts == other_user.allowed_client_hosts)
&& (access == other_user.access) && (access_with_grant_option == other_user.access_with_grant_option)
&& (granted_roles == other_user.granted_roles) && (granted_roles_with_admin_option == other_user.granted_roles_with_admin_option)
&& (default_roles == other_user.default_roles) && (settings == other_user.settings);
&& (access == other_user.access) && (granted_roles == other_user.granted_roles) && (default_roles == other_user.default_roles)
&& (settings == other_user.settings);
}
}

View File

@ -3,11 +3,10 @@
#include <Access/IAccessEntity.h>
#include <Access/Authentication.h>
#include <Access/AllowedClientHosts.h>
#include <Access/AccessRights.h>
#include <Access/GrantedAccess.h>
#include <Access/GrantedRoles.h>
#include <Access/ExtendedRoleSet.h>
#include <Access/SettingsProfileElement.h>
#include <Core/UUID.h>
#include <boost/container/flat_set.hpp>
namespace DB
@ -18,10 +17,8 @@ struct User : public IAccessEntity
{
Authentication authentication;
AllowedClientHosts allowed_client_hosts = AllowedClientHosts::AnyHostTag{};
AccessRights access;
AccessRights access_with_grant_option;
boost::container::flat_set<UUID> granted_roles;
boost::container::flat_set<UUID> granted_roles_with_admin_option;
GrantedAccess access;
GrantedRoles granted_roles;
ExtendedRoleSet default_roles = ExtendedRoleSet::AllTag{};
SettingsProfileElements settings;

View File

@ -151,30 +151,30 @@ namespace
}
}
user->access.grant(AccessType::ALL); /// By default all databases are accessible.
/// By default all databases are accessible
/// and the user can grant everything he has.
user->access.grantWithGrantOption(AccessType::ALL);
if (databases)
{
user->access.revoke(AccessFlags::allFlags() - AccessFlags::allGlobalFlags());
user->access.grant(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG);
user->access.grantWithGrantOption(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG);
for (const String & database : *databases)
user->access.grant(AccessFlags::allFlags(), database);
user->access.grantWithGrantOption(AccessFlags::allFlags(), database);
}
if (dictionaries)
{
user->access.revoke(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG);
for (const String & dictionary : *dictionaries)
user->access.grant(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG, dictionary);
user->access.grantWithGrantOption(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG, dictionary);
}
user->access_with_grant_option = user->access; /// By default the user can grant everything he has.
bool access_management = config.getBool(user_config + ".access_management", false);
if (!access_management)
{
user->access.revoke(AccessType::ACCESS_MANAGEMENT);
user->access_with_grant_option.clear();
user->access.revokeGrantOption(AccessType::ALL);
}
return user;

View File

@ -18,6 +18,8 @@ SRCS(
EnabledRowPolicies.cpp
EnabledSettings.cpp
ExtendedRoleSet.cpp
GrantedAccess.cpp
GrantedRoles.cpp
IAccessEntity.cpp
IAccessStorage.cpp
MemoryAccessStorage.cpp

View File

@ -48,7 +48,7 @@ namespace
if (default_roles)
{
if (!query.alter && !default_roles->all)
boost::range::copy(default_roles->getMatchingIDs(), std::inserter(user.granted_roles, user.granted_roles.end()));
user.granted_roles.grant(default_roles->getMatchingIDs());
InterpreterSetRoleQuery::updateUserSetDefaultRoles(user, *default_roles);
}

View File

@ -23,14 +23,16 @@ namespace
{
if (query.kind == Kind::GRANT)
{
grantee.access.grant(query.access_rights_elements, current_database);
if (query.grant_option)
grantee.access_with_grant_option.grant(query.access_rights_elements, current_database);
grantee.access.grantWithGrantOption(query.access_rights_elements, current_database);
else
grantee.access.grant(query.access_rights_elements, current_database);
}
else
{
grantee.access_with_grant_option.revoke(query.access_rights_elements, current_database);
if (!query.grant_option)
if (query.grant_option)
grantee.access.revokeGrantOption(query.access_rights_elements, current_database);
else
grantee.access.revoke(query.access_rights_elements, current_database);
}
}
@ -39,18 +41,21 @@ namespace
{
if (query.kind == Kind::GRANT)
{
boost::range::copy(roles_from_query, std::inserter(grantee.granted_roles, grantee.granted_roles.end()));
if (query.admin_option)
boost::range::copy(roles_from_query, std::inserter(grantee.granted_roles_with_admin_option, grantee.granted_roles_with_admin_option.end()));
grantee.granted_roles.grantWithAdminOption(roles_from_query);
else
grantee.granted_roles.grant(roles_from_query);
}
else
{
for (const UUID & role_from_query : roles_from_query)
if (query.admin_option)
grantee.granted_roles.revokeAdminOption(roles_from_query);
else
grantee.granted_roles.revoke(roles_from_query);
if constexpr (std::is_same_v<T, User>)
{
grantee.granted_roles_with_admin_option.erase(role_from_query);
if (!query.admin_option)
grantee.granted_roles.erase(role_from_query);
if constexpr (std::is_same_v<T, User>)
for (const UUID & role_from_query : roles_from_query)
grantee.default_roles.ids.erase(role_from_query);
}
}

View File

@ -42,7 +42,7 @@ void InterpreterSetRoleQuery::setRole(const ASTSetRoleQuery & query)
std::vector<UUID> new_current_roles;
if (roles_from_query.all)
{
for (const auto & id : user->granted_roles)
for (const auto & id : user->granted_roles.roles)
if (roles_from_query.match(id))
new_current_roles.push_back(id);
}
@ -50,7 +50,7 @@ void InterpreterSetRoleQuery::setRole(const ASTSetRoleQuery & query)
{
for (const auto & id : roles_from_query.getMatchingIDs())
{
if (!user->granted_roles.count(id))
if (!user->granted_roles.roles.contains(id))
throw Exception("Role should be granted to set current", ErrorCodes::SET_NON_GRANTED_ROLE);
new_current_roles.push_back(id);
}
@ -85,7 +85,7 @@ void InterpreterSetRoleQuery::updateUserSetDefaultRoles(User & user, const Exten
{
for (const auto & id : roles_from_query.getMatchingIDs())
{
if (!user.granted_roles.count(id))
if (!user.granted_roles.roles.contains(id))
throw Exception("Role should be granted to set default", ErrorCodes::SET_NON_GRANTED_ROLE);
}
}

View File

@ -10,8 +10,6 @@
#include <Access/AccessControlManager.h>
#include <Access/User.h>
#include <Access/Role.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
namespace DB
@ -23,37 +21,6 @@ namespace ErrorCodes
namespace
{
std::vector<AccessRightsElements> groupByTable(AccessRightsElements && elements)
{
using Key = std::tuple<String, bool, String, bool>;
std::map<Key, AccessRightsElements> grouping_map;
for (auto & element : elements)
{
Key key(element.database, element.any_database, element.table, element.any_table);
grouping_map[key].emplace_back(std::move(element));
}
std::vector<AccessRightsElements> res;
res.reserve(grouping_map.size());
boost::range::copy(grouping_map | boost::adaptors::map_values, std::back_inserter(res));
return res;
}
struct GroupedGrantsAndPartialRevokes
{
std::vector<AccessRightsElements> grants;
std::vector<AccessRightsElements> partial_revokes;
};
GroupedGrantsAndPartialRevokes groupByTable(AccessRights::Elements && elements)
{
GroupedGrantsAndPartialRevokes res;
res.grants = groupByTable(std::move(elements.grants));
res.partial_revokes = groupByTable(std::move(elements.partial_revokes));
return res;
}
template <typename T>
ASTs getGrantQueriesImpl(
const T & grantee,
@ -65,35 +32,51 @@ namespace
std::shared_ptr<ASTExtendedRoleSet> to_roles = std::make_shared<ASTExtendedRoleSet>();
to_roles->names.push_back(grantee.getName());
for (bool grant_option : {true, false})
{
if (!grant_option && (grantee.access == grantee.access_with_grant_option))
continue;
const auto & access_rights = grant_option ? grantee.access_with_grant_option : grantee.access;
const auto grouped_elements = groupByTable(access_rights.getElements());
auto grants_and_partial_revokes = grantee.access.getGrantsAndPartialRevokes();
for (bool grant_option : {false, true})
{
using Kind = ASTGrantQuery::Kind;
for (Kind kind : {Kind::GRANT, Kind::REVOKE})
{
for (const auto & elements : (kind == Kind::GRANT ? grouped_elements.grants : grouped_elements.partial_revokes))
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)
{
auto 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;
grant_query->access_rights_elements = elements;
res.push_back(std::move(grant_query));
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));
}
}
}
for (bool admin_option : {true, false})
{
if (!admin_option && (grantee.granted_roles == grantee.granted_roles_with_admin_option))
continue;
auto grants_roles = grantee.granted_roles.getGrants();
const auto & roles = admin_option ? grantee.granted_roles_with_admin_option : grantee.granted_roles;
for (bool admin_option : {false, true})
{
const auto & roles = admin_option ? grants_roles.grants_with_admin_option : grants_roles.grants;
if (roles.empty())
continue;

View File

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

View File

@ -1,10 +1,6 @@
#include <Parsers/ASTGrantQuery.h>
#include <Parsers/ASTExtendedRoleSet.h>
#include <Common/quoteString.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <map>
namespace DB
@ -16,61 +12,13 @@ namespace ErrorCodes
namespace
{
using KeywordToColumnsMap = std::map<std::string_view /* keyword */, std::vector<std::string_view> /* columns */>;
using TableToAccessMap = std::map<String /* database_and_table_name */, KeywordToColumnsMap>;
TableToAccessMap prepareTableToAccessMap(const AccessRightsElements & elements)
void formatColumnNames(const Strings & columns, const IAST::FormatSettings & settings)
{
TableToAccessMap res;
for (const auto & element : elements)
{
String database_and_table_name;
if (element.any_database)
{
if (element.any_table)
database_and_table_name = "*.*";
else
database_and_table_name = "*." + backQuoteIfNeed(element.table);
}
else if (element.database.empty())
{
if (element.any_table)
database_and_table_name = "*";
else
database_and_table_name = backQuoteIfNeed(element.table);
}
else
{
if (element.any_table)
database_and_table_name = backQuoteIfNeed(element.database) + ".*";
else
database_and_table_name = backQuoteIfNeed(element.database) + "." + backQuoteIfNeed(element.table);
}
KeywordToColumnsMap & keyword_to_columns = res[database_and_table_name];
for (const auto & keyword : element.access_flags.toKeywords())
boost::range::push_back(keyword_to_columns[keyword], element.columns);
}
for (auto & keyword_to_columns : res | boost::adaptors::map_values)
{
for (auto & columns : keyword_to_columns | boost::adaptors::map_values)
boost::range::sort(columns);
}
return res;
}
void formatColumnNames(const std::vector<std::string_view> & columns, const IAST::FormatSettings & settings)
{
if (columns.empty())
return;
settings.ostr << "(";
bool need_comma_after_column_name = false;
bool need_comma = false;
for (const auto & column : columns)
{
if (std::exchange(need_comma_after_column_name, true))
if (std::exchange(need_comma, true))
settings.ostr << ", ";
settings.ostr << backQuoteIfNeed(column);
}
@ -80,20 +28,50 @@ namespace
void formatAccessRightsElements(const AccessRightsElements & elements, const IAST::FormatSettings & settings)
{
bool need_comma = false;
for (const auto & [database_and_table, keyword_to_columns] : prepareTableToAccessMap(elements))
bool no_output = true;
for (size_t i = 0; i != elements.size(); ++i)
{
for (const auto & [keyword, columns] : keyword_to_columns)
const auto & element = elements[i];
auto keywords = element.access_flags.toKeywords();
if (keywords.empty() || (!element.any_column && element.columns.empty()))
continue;
for (const auto & keyword : keywords)
{
if (std::exchange(need_comma, true))
if (!std::exchange(no_output, false))
settings.ostr << ", ";
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << keyword << (settings.hilite ? IAST::hilite_none : "");
formatColumnNames(columns, settings);
if (!element.any_column)
formatColumnNames(element.columns, settings);
}
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " ON " << (settings.hilite ? IAST::hilite_none : "") << database_and_table;
bool next_element_on_same_db_and_table = false;
if (i != elements.size() - 1)
{
const auto & next_element = elements[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 (!next_element_on_same_db_and_table)
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " ON " << (settings.hilite ? IAST::hilite_none : "");
if (element.any_database)
settings.ostr << "*.";
else if (!element.database.empty())
settings.ostr << backQuoteIfNeed(element.database) + ".";
if (element.any_table)
settings.ostr << "*";
else
settings.ostr << backQuoteIfNeed(element.table);
}
}
if (no_output)
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "USAGE ON " << (settings.hilite ? IAST::hilite_none : "") << "*.*";
}
@ -134,7 +112,7 @@ void ASTGrantQuery::formatImpl(const FormatSettings & settings, FormatState &, F
settings.ostr << (settings.hilite ? hilite_keyword : "") << " ADMIN OPTION FOR" << (settings.hilite ? hilite_none : "");
}
if ((!!roles + !access_rights_elements.empty()) != 1)
if (roles && !access_rights_elements.empty())
throw Exception("Either roles or access rights elements should be set", ErrorCodes::LOGICAL_ERROR);
settings.ostr << " ";

View File

@ -1,11 +1,11 @@
CREATE USER test_user_01073
A
B
GRANT ALTER DELETE, INSERT 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 ALTER DELETE ON *.* TO test_user_01073
GRANT SELECT(col1) ON db4.table TO test_user_01073
GRANT ALTER DELETE ON *.* TO test_user_01073