Improvements in implementations of the classes AccessRights and GrantedRoles.

This commit is contained in:
Vitaly Baranov 2021-02-27 01:37:00 +03:00
parent 084bd03672
commit d6e0342c30
60 changed files with 944 additions and 811 deletions

View File

@ -403,7 +403,7 @@ void AccessControlManager::checkSettingNameIsAllowed(const std::string_view & se
std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(
const UUID & user_id,
const boost::container::flat_set<UUID> & current_roles,
const std::vector<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
@ -411,7 +411,7 @@ std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(
{
ContextAccessParams params;
params.user_id = user_id;
params.current_roles = current_roles;
params.current_roles.insert(current_roles.begin(), current_roles.end());
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
@ -444,8 +444,8 @@ std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(cons
std::shared_ptr<const EnabledRoles> AccessControlManager::getEnabledRoles(
const boost::container::flat_set<UUID> & current_roles,
const boost::container::flat_set<UUID> & current_roles_with_admin_option) const
const std::vector<UUID> & current_roles,
const std::vector<UUID> & current_roles_with_admin_option) const
{
return role_cache->getEnabledRoles(current_roles, current_roles_with_admin_option);
}

View File

@ -114,7 +114,7 @@ public:
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const boost::container::flat_set<UUID> & current_roles,
const std::vector<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
@ -123,8 +123,8 @@ public:
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;
const std::vector<UUID> & current_roles,
const std::vector<UUID> & current_roles_with_admin_option) const;
std::shared_ptr<const EnabledRowPolicies> getEnabledRowPolicies(
const UUID & user_id,

View File

@ -7,16 +7,19 @@
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
namespace
{
using Kind = AccessRightsElementWithOptions::Kind;
struct ProtoElement
{
AccessFlags access_flags;
boost::container::small_vector<std::string_view, 3> full_name;
bool grant_option = false;
Kind kind = Kind::GRANT;
bool is_partial_revoke = false;
friend bool operator<(const ProtoElement & left, const ProtoElement & right)
{
@ -43,8 +46,8 @@ namespace
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.is_partial_revoke != right.is_partial_revoke)
return right.is_partial_revoke;
if (left.grant_option != right.grant_option)
return right.grant_option;
@ -55,12 +58,12 @@ namespace
return (left.access_flags < right.access_flags);
}
AccessRightsElementWithOptions getResult() const
AccessRightsElement getResult() const
{
AccessRightsElementWithOptions res;
AccessRightsElement res;
res.access_flags = access_flags;
res.grant_option = grant_option;
res.kind = kind;
res.is_partial_revoke = is_partial_revoke;
switch (full_name.size())
{
case 0:
@ -105,11 +108,11 @@ namespace
class ProtoElements : public std::vector<ProtoElement>
{
public:
AccessRightsElementsWithOptions getResult() const
AccessRightsElements getResult() const
{
ProtoElements sorted = *this;
boost::range::sort(sorted);
AccessRightsElementsWithOptions res;
AccessRightsElements res;
res.reserve(sorted.size());
for (size_t i = 0; i != sorted.size();)
@ -144,7 +147,7 @@ namespace
{
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);
|| (element.is_partial_revoke != start_element.is_partial_revoke);
});
return it - (begin() + start);
@ -153,7 +156,7 @@ namespace
/// 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
void appendResultWithElementsWithDifferenceInColumnOnly(size_t start, size_t count, AccessRightsElements & res) const
{
const auto * pbegin = data() + start;
const auto * pend = pbegin + count;
@ -180,7 +183,7 @@ namespace
res.emplace_back();
auto & back = res.back();
back.grant_option = pbegin->grant_option;
back.kind = pbegin->kind;
back.is_partial_revoke = pbegin->is_partial_revoke;
back.any_database = false;
back.database = pbegin->full_name[0];
back.any_table = false;
@ -515,10 +518,10 @@ private:
auto grants = flags - parent_fl;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
res.push_back(ProtoElement{revokes, full_name, false, true});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
res.push_back(ProtoElement{grants, full_name, false, false});
if (node.children)
{
@ -550,16 +553,16 @@ private:
auto grants = flags - parent_fl - grants_go;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
res.push_back(ProtoElement{revokes, full_name, false, true});
if (revokes_go)
res.push_back(ProtoElement{revokes_go, full_name, true, Kind::REVOKE});
res.push_back(ProtoElement{revokes_go, full_name, true, true});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
res.push_back(ProtoElement{grants, full_name, false, false});
if (grants_go)
res.push_back(ProtoElement{grants_go, full_name, true, Kind::GRANT});
res.push_back(ProtoElement{grants_go, full_name, true, false});
if (node && node->children)
{
@ -774,8 +777,10 @@ void AccessRights::grantImpl(const AccessFlags & flags, const Args &... args)
}
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElement & element)
void AccessRights::grantImplHelper(const AccessRightsElement & element)
{
assert(!element.is_partial_revoke);
assert(!element.grant_option || with_grant_option);
if (element.any_database)
grantImpl<with_grant_option>(element.access_flags);
else if (element.any_table)
@ -786,6 +791,24 @@ void AccessRights::grantImpl(const AccessRightsElement & element)
grantImpl<with_grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElement & element)
{
if (element.is_partial_revoke)
throw Exception("A partial revoke should be revoked, not granted", ErrorCodes::BAD_ARGUMENTS);
if constexpr (with_grant_option)
{
grantImplHelper<true>(element);
}
else
{
if (element.grant_option)
grantImplHelper<true>(element);
else
grantImplHelper<false>(element);
}
}
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElements & elements)
{
@ -830,8 +853,9 @@ void AccessRights::revokeImpl(const AccessFlags & flags, const Args &... args)
}
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElement & element)
void AccessRights::revokeImplHelper(const AccessRightsElement & element)
{
assert(!element.grant_option || grant_option);
if (element.any_database)
revokeImpl<grant_option>(element.access_flags);
else if (element.any_table)
@ -842,6 +866,22 @@ void AccessRights::revokeImpl(const AccessRightsElement & element)
revokeImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElement & element)
{
if constexpr (grant_option)
{
revokeImplHelper<true>(element);
}
else
{
if (element.grant_option)
revokeImplHelper<true>(element);
else
revokeImplHelper<false>(element);
}
}
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElements & elements)
{
@ -868,7 +908,7 @@ void AccessRights::revokeGrantOption(const AccessRightsElement & element) { revo
void AccessRights::revokeGrantOption(const AccessRightsElements & elements) { revokeImpl<true>(elements); }
AccessRightsElementsWithOptions AccessRights::getElements() const
AccessRightsElements AccessRights::getElements() const
{
#if 0
logTree();
@ -903,8 +943,9 @@ bool AccessRights::isGrantedImpl(const AccessFlags & flags, const Args &... args
}
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElement & element) const
bool AccessRights::isGrantedImplHelper(const AccessRightsElement & element) const
{
assert(!element.grant_option || grant_option);
if (element.any_database)
return isGrantedImpl<grant_option>(element.access_flags);
else if (element.any_table)
@ -915,6 +956,22 @@ bool AccessRights::isGrantedImpl(const AccessRightsElement & element) const
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElement & element) const
{
if constexpr (grant_option)
{
return isGrantedImplHelper<true>(element);
}
else
{
if (element.grant_option)
return isGrantedImplHelper<true>(element);
else
return isGrantedImplHelper<false>(element);
}
}
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElements & elements) const
{

View File

@ -30,7 +30,7 @@ public:
String toString() const;
/// Returns the information about all the access granted.
AccessRightsElementsWithOptions getElements() const;
AccessRightsElements getElements() const;
/// Grants access on a specified database/table/column.
/// Does nothing if the specified access has been already granted.
@ -119,12 +119,15 @@ private:
template <bool with_grant_option, typename... Args>
void grantImpl(const AccessFlags & flags, const Args &... args);
template <bool with_grant_options>
template <bool with_grant_option>
void grantImpl(const AccessRightsElement & element);
template <bool with_grant_options>
template <bool with_grant_option>
void grantImpl(const AccessRightsElements & elements);
template <bool with_grant_option>
void grantImplHelper(const AccessRightsElement & element);
template <bool grant_option, typename... Args>
void revokeImpl(const AccessFlags & flags, const Args &... args);
@ -134,6 +137,9 @@ private:
template <bool grant_option>
void revokeImpl(const AccessRightsElements & elements);
template <bool grant_option>
void revokeImplHelper(const AccessRightsElement & element);
template <bool grant_option, typename... Args>
bool isGrantedImpl(const AccessFlags & flags, const Args &... args) const;
@ -143,6 +149,9 @@ private:
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElements & elements) const;
template <bool grant_option>
bool isGrantedImplHelper(const AccessRightsElement & element) const;
void logTree() const;
struct Node;

View File

@ -1,169 +1,162 @@
#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
{
using Kind = AccessRightsElementWithOptions::Kind;
String formatOptions(bool grant_option, Kind kind, const String & inner_part)
void formatColumnNames(const Strings & columns, String & result)
{
if (kind == Kind::REVOKE)
result += "(";
bool need_comma = false;
for (const auto & column : columns)
{
if (grant_option)
return "REVOKE GRANT OPTION " + inner_part;
else
return "REVOKE " + inner_part;
}
else
{
if (grant_option)
return "GRANT " + inner_part + " WITH GRANT OPTION";
else
return "GRANT " + inner_part;
if (need_comma)
result += ", ";
need_comma = true;
result += backQuoteIfNeed(column);
}
result += ")";
}
String formatONClause(const String & database, bool any_database, const String & table, bool any_table)
void formatONClause(const String & database, bool any_database, const String & table, bool any_table, String & result)
{
String msg = "ON ";
result += "ON ";
if (any_database)
msg += "*.";
else if (!database.empty())
msg += backQuoteIfNeed(database) + ".";
if (any_table)
msg += "*";
{
result += "*.*";
}
else
msg += backQuoteIfNeed(table);
return msg;
{
if (!database.empty())
{
result += backQuoteIfNeed(database);
result += ".";
}
if (any_table)
result += "*";
else
result += backQuoteIfNeed(table);
}
}
String formatAccessFlagsWithColumns(const AccessFlags & access_flags, const Strings & columns, bool any_column)
void formatOptions(bool grant_option, bool is_partial_revoke, String & result)
{
String columns_in_parentheses;
if (is_partial_revoke)
{
if (grant_option)
result.insert(0, "REVOKE GRANT OPTION ");
else
result.insert(0, "REVOKE ");
}
else
{
if (grant_option)
result.insert(0, "GRANT ").append(" WITH GRANT OPTION");
else
result.insert(0, "GRANT ");
}
}
void formatAccessFlagsWithColumns(const AccessFlags & access_flags, const Strings & columns, bool any_column, String & result)
{
String columns_as_str;
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);
result += "USAGE";
return;
}
columns_in_parentheses += ")";
formatColumnNames(columns, columns_as_str);
}
auto keywords = access_flags.toKeywords();
if (keywords.empty())
return "USAGE";
{
result += "USAGE";
return;
}
String msg;
bool need_comma = false;
for (const std::string_view & keyword : keywords)
{
if (!msg.empty())
msg += ", ";
msg += String{keyword} + columns_in_parentheses;
}
return msg;
}
}
String AccessRightsElement::toString() const
{
return formatAccessFlagsWithColumns(access_flags, columns, any_column) + " " + formatONClause(database, any_database, table, any_table);
}
String AccessRightsElementWithOptions::toString() const
{
return formatOptions(grant_option, kind, AccessRightsElement::toString());
}
String AccessRightsElements::toString() const
{
if (empty())
return "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_table = false;
if (i != size() - 1)
{
const auto & next_element = (*this)[i + 1];
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 (need_comma)
result.append(", ");
need_comma = true;
result += keyword;
result += columns_as_str;
}
}
return res;
}
String AccessRightsElementsWithOptions::toString() const
{
if (empty())
return "GRANT USAGE ON *.*";
String res;
String inner_part;
for (size_t i = 0; i != size(); ++i)
String toStringImpl(const AccessRightsElement & element, bool with_options)
{
const auto & element = (*this)[i];
String result;
formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column, result);
result += " ";
formatONClause(element.database, element.any_database, element.table, element.any_table, result);
if (with_options)
formatOptions(element.grant_option, element.is_partial_revoke, result);
return result;
}
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)
String toStringImpl(const AccessRightsElements & elements, bool with_options)
{
const auto & next_element = (*this)[i + 1];
if (elements.empty())
return with_options ? "GRANT USAGE ON *.*" : "USAGE ON *.*";
String result;
String part;
for (size_t i = 0; i != elements.size(); ++i)
{
const auto & element = elements[i];
if (!part.empty())
part += ", ";
formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column, part);
bool next_element_uses_same_table_and_options = false;
if (i != elements.size() - 1)
{
const auto & next_element = elements[i + 1];
if (element.sameDatabaseAndTable(next_element) && element.sameOptions(next_element))
next_element_uses_same_mode_and_table = true;
next_element_uses_same_table_and_options = true;
}
if (!next_element_uses_same_mode_and_table)
if (!next_element_uses_same_table_and_options)
{
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();
part += " ";
formatONClause(element.database, element.any_database, element.table, element.any_table, part);
if (with_options)
formatOptions(element.grant_option, element.is_partial_revoke, part);
if (result.empty())
result = std::move(part);
else
result.append(", ").append(part);
part.clear();
}
}
return res;
return result;
}
}
String AccessRightsElement::toString() const { return toStringImpl(*this, true); }
String AccessRightsElement::toStringWithoutOptions() const { return toStringImpl(*this, false); }
String AccessRightsElements::toString() const { return toStringImpl(*this, true); }
String AccessRightsElements::toStringWithoutOptions() const { return toStringImpl(*this, false); }
void AccessRightsElements::eraseNonGrantable()
{
boost::range::remove_erase_if(*this, [](AccessRightsElement & element)
{
element.eraseNonGrantable();
return element.empty();
});
}
}

View File

@ -16,6 +16,8 @@ struct AccessRightsElement
bool any_database = true;
bool any_table = true;
bool any_column = true;
bool grant_option = false;
bool is_partial_revoke = false;
AccessRightsElement() = default;
AccessRightsElement(const AccessRightsElement &) = default;
@ -73,7 +75,7 @@ struct AccessRightsElement
bool empty() const { return !access_flags || (!any_column && columns.empty()); }
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns); }
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns, grant_option, is_partial_revoke); }
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); }
@ -83,44 +85,36 @@ struct AccessRightsElement
&& (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);
/// Resets flags which cannot be granted.
void removeNonGrantableFlags();
/// Returns a human-readable representation like "SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
struct AccessRightsElementWithOptions : public AccessRightsElement
{
bool grant_option = false;
enum class Kind
bool sameOptions(const AccessRightsElement & other) const
{
GRANT,
REVOKE,
};
Kind kind = Kind::GRANT;
bool sameOptions(const AccessRightsElementWithOptions & other) const
{
return (grant_option == other.grant_option) && (kind == other.kind);
return (grant_option == other.grant_option) && (is_partial_revoke == other.is_partial_revoke);
}
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); }
/// Resets flags which cannot be granted.
void removeNonGrantableFlags();
void eraseNonGrantable()
{
if (!any_column)
access_flags &= AccessFlags::allFlagsGrantableOnColumnLevel();
else if (!any_table)
access_flags &= AccessFlags::allFlagsGrantableOnTableLevel();
else if (!any_database)
access_flags &= AccessFlags::allFlagsGrantableOnDatabaseLevel();
else
access_flags &= AccessFlags::allFlagsGrantableOnGlobalLevel();
}
bool isEmptyDatabase() const { return !any_database && database.empty(); }
/// If the database is empty, replaces it with `current_database`. Otherwise does nothing.
void replaceEmptyDatabase(const String & current_database)
{
if (isEmptyDatabase())
database = current_database;
}
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
String toStringWithoutOptions() const;
};
@ -130,77 +124,29 @@ class AccessRightsElements : public std::vector<AccessRightsElement>
public:
bool empty() const { return std::all_of(begin(), end(), [](const AccessRightsElement & e) { return e.empty(); }); }
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
bool sameDatabaseAndTable() const
{
return (size() < 2) || std::all_of(std::next(begin()), end(), [this](const AccessRightsElement & e) { return e.sameDatabaseAndTable(front()); });
}
bool sameOptions() const
{
return (size() < 2) || std::all_of(std::next(begin()), end(), [this](const AccessRightsElement & e) { return e.sameOptions(front()); });
}
/// Resets flags which cannot be granted.
void removeNonGrantableFlags();
void eraseNonGrantable();
/// If the database is empty, replaces it with `current_database`. Otherwise does nothing.
void replaceEmptyDatabase(const String & current_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(current_database);
}
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
String toStringWithoutOptions() const;
};
class AccessRightsElementsWithOptions : public std::vector<AccessRightsElementWithOptions>
{
public:
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
/// Resets flags which cannot be granted.
void removeNonGrantableFlags();
/// 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);
}
inline void AccessRightsElement::removeNonGrantableFlags()
{
if (!any_column)
access_flags &= AccessFlags::allFlagsGrantableOnColumnLevel();
else if (!any_table)
access_flags &= AccessFlags::allFlagsGrantableOnTableLevel();
else if (!any_database)
access_flags &= AccessFlags::allFlagsGrantableOnDatabaseLevel();
else
access_flags &= AccessFlags::allFlagsGrantableOnGlobalLevel();
}
inline void AccessRightsElementWithOptions::removeNonGrantableFlags()
{
if (kind == Kind::GRANT)
AccessRightsElement::removeNonGrantableFlags();
}
inline void AccessRightsElements::removeNonGrantableFlags()
{
for (auto & element : *this)
element.removeNonGrantableFlags();
}
inline void AccessRightsElementsWithOptions::removeNonGrantableFlags()
{
for (auto & element : *this)
element.removeNonGrantableFlags();
}
}

View File

@ -177,28 +177,18 @@ void ContextAccess::setUser(const UserPtr & user_) const
user_name = user->getName();
trace_log = &Poco::Logger::get("ContextAccess (" + user_name + ")");
boost::container::flat_set<UUID> current_roles, current_roles_with_admin_option;
std::vector<UUID> current_roles, current_roles_with_admin_option;
if (params.use_default_roles)
{
for (const UUID & id : user->granted_roles.roles)
{
if (user->default_roles.match(id))
current_roles.emplace(id);
}
current_roles = user->granted_roles.findGranted(user->default_roles);
current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(user->default_roles);
}
else
{
boost::range::set_intersection(
params.current_roles,
user->granted_roles.roles,
std::inserter(current_roles, current_roles.end()));
current_roles = user->granted_roles.findGranted(params.current_roles);
current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(params.current_roles);
}
boost::range::set_intersection(
current_roles,
user->granted_roles.roles_with_admin_option,
std::inserter(current_roles_with_admin_option, current_roles_with_admin_option.end()));
subscription_for_roles_changes = {};
enabled_roles = manager->getEnabledRoles(current_roles, current_roles_with_admin_option);
subscription_for_roles_changes = enabled_roles->subscribeForChanges([this](const std::shared_ptr<const EnabledRolesInfo> & roles_info_)
@ -331,47 +321,13 @@ std::shared_ptr<const AccessRights> ContextAccess::getAccessRightsWithImplicit()
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessFlags & flags) const
{
return checkAccessImpl2<throw_if_denied, grant_option>(flags);
}
template <bool throw_if_denied, bool grant_option, typename... Args>
bool ContextAccess::checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
return checkAccessImpl2<throw_if_denied, grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessRightsElement & element) const
{
if (element.any_database)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags);
else if (element.any_table)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database);
else if (element.any_column)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database, element.table);
else
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!checkAccessImpl<throw_if_denied, grant_option>(element))
return false;
return true;
}
template <bool throw_if_denied, bool grant_option, typename... Args>
bool ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &... args) const
bool ContextAccess::checkAccessImplHelper(const AccessFlags & flags, const Args &... args) const
{
auto access_granted = [&]
{
if (trace_log)
LOG_TRACE(trace_log, "Access granted: {}{}", (AccessRightsElement{flags, args...}.toString()),
LOG_TRACE(trace_log, "Access granted: {}{}", (AccessRightsElement{flags, args...}.toStringWithoutOptions()),
(grant_option ? " WITH GRANT OPTION" : ""));
return true;
};
@ -379,7 +335,7 @@ bool ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &...
auto access_denied = [&](const String & error_msg, int error_code [[maybe_unused]])
{
if (trace_log)
LOG_TRACE(trace_log, "Access denied: {}{}", (AccessRightsElement{flags, args...}.toString()),
LOG_TRACE(trace_log, "Access denied: {}{}", (AccessRightsElement{flags, args...}.toStringWithoutOptions()),
(grant_option ? " WITH GRANT OPTION" : ""));
if constexpr (throw_if_denied)
throw Exception(getUserName() + ": " + error_msg, error_code);
@ -415,13 +371,13 @@ bool ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &...
"Not enough privileges. "
"The required privileges have been granted, but without grant option. "
"To execute this query it's necessary to have grant "
+ AccessRightsElement{flags, args...}.toString() + " WITH GRANT OPTION",
+ AccessRightsElement{flags, args...}.toStringWithoutOptions() + " WITH GRANT OPTION",
ErrorCodes::ACCESS_DENIED);
}
return access_denied(
"Not enough privileges. To execute this query it's necessary to have grant "
+ AccessRightsElement{flags, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""),
+ AccessRightsElement{flags, args...}.toStringWithoutOptions() + (grant_option ? " WITH GRANT OPTION" : ""),
ErrorCodes::ACCESS_DENIED);
}
@ -478,6 +434,56 @@ bool ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &...
return access_granted();
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessFlags & flags) const
{
return checkAccessImplHelper<throw_if_denied, grant_option>(flags);
}
template <bool throw_if_denied, bool grant_option, typename... Args>
bool ContextAccess::checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
return checkAccessImplHelper<throw_if_denied, grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImplHelper(const AccessRightsElement & element) const
{
assert(!element.grant_option || grant_option);
if (element.any_database)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags);
else if (element.any_table)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database);
else if (element.any_column)
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database, element.table);
else
return checkAccessImpl<throw_if_denied, grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessRightsElement & element) const
{
if constexpr (grant_option)
{
return checkAccessImplHelper<throw_if_denied, true>(element);
}
else
{
if (element.grant_option)
return checkAccessImplHelper<throw_if_denied, true>(element);
else
return checkAccessImplHelper<throw_if_denied, false>(element);
}
}
template <bool throw_if_denied, bool grant_option>
bool ContextAccess::checkAccessImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!checkAccessImpl<throw_if_denied, grant_option>(element))
return false;
return true;
}
bool ContextAccess::isGranted(const AccessFlags & flags) const { return checkAccessImpl<false, false>(flags); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database) const { return checkAccessImpl<false, false>(flags, database); }
@ -516,44 +522,8 @@ void ContextAccess::checkGrantOption(const AccessRightsElement & element) const
void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl<true, true>(elements); }
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id) const
{
return checkAdminOptionImpl2<throw_if_denied>(to_array(role_id), [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const String & role_name) const
{
return checkAdminOptionImpl2<throw_if_denied>(to_array(role_id), [&role_name](const UUID &, size_t) { return std::optional<String>{role_name}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const
{
return checkAdminOptionImpl2<throw_if_denied>(to_array(role_id), [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids) const
{
return checkAdminOptionImpl2<throw_if_denied>(role_ids, [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const
{
return checkAdminOptionImpl2<throw_if_denied>(role_ids, [&names_of_roles](const UUID &, size_t i) { return std::optional<String>{names_of_roles[i]}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const
{
return checkAdminOptionImpl2<throw_if_denied>(role_ids, [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
template <bool throw_if_denied, typename Container, typename GetNameFunction>
bool ContextAccess::checkAdminOptionImpl2(const Container & role_ids, const GetNameFunction & get_name_function) const
bool ContextAccess::checkAdminOptionImplHelper(const Container & role_ids, const GetNameFunction & get_name_function) const
{
if (!std::size(role_ids) || is_full_access)
return true;
@ -605,6 +575,42 @@ bool ContextAccess::checkAdminOptionImpl2(const Container & role_ids, const GetN
return true;
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id) const
{
return checkAdminOptionImplHelper<throw_if_denied>(to_array(role_id), [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const String & role_name) const
{
return checkAdminOptionImplHelper<throw_if_denied>(to_array(role_id), [&role_name](const UUID &, size_t) { return std::optional<String>{role_name}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const
{
return checkAdminOptionImplHelper<throw_if_denied>(to_array(role_id), [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids) const
{
return checkAdminOptionImplHelper<throw_if_denied>(role_ids, [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const
{
return checkAdminOptionImplHelper<throw_if_denied>(role_ids, [&names_of_roles](const UUID &, size_t i) { return std::optional<String>{names_of_roles[i]}; });
}
template <bool throw_if_denied>
bool ContextAccess::checkAdminOptionImpl(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const
{
return checkAdminOptionImplHelper<throw_if_denied>(role_ids, [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
bool ContextAccess::hasAdminOption(const UUID & role_id) const { return checkAdminOptionImpl<false>(role_id); }
bool ContextAccess::hasAdminOption(const UUID & role_id, const String & role_name) const { return checkAdminOptionImpl<false>(role_id, role_name); }
bool ContextAccess::hasAdminOption(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const { return checkAdminOptionImpl<false>(role_id, names_of_roles); }

View File

@ -99,25 +99,6 @@ public:
std::shared_ptr<const AccessRights> getAccessRights() const;
std::shared_ptr<const AccessRights> getAccessRightsWithImplicit() const;
/// Checks if a specified access is granted.
bool isGranted(const AccessFlags & flags) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
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) const;
bool isGranted(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;
@ -138,6 +119,26 @@ public:
void checkGrantOption(const AccessRightsElement & element) const;
void checkGrantOption(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted, and returns false if not.
/// Empty database means the current database.
bool isGranted(const AccessFlags & flags) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
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) const;
bool isGranted(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 role is granted with admin option, and throws an exception if not.
void checkAdminOption(const UUID & role_id) const;
void checkAdminOption(const UUID & role_id, const String & role_name) const;
@ -146,6 +147,7 @@ public:
void checkAdminOption(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const;
void checkAdminOption(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const;
/// Checks if a specified role is granted with admin option, and returns false if not.
bool hasAdminOption(const UUID & role_id) const;
bool hasAdminOption(const UUID & role_id, const String & role_name) const;
bool hasAdminOption(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const;
@ -180,7 +182,10 @@ private:
bool checkAccessImpl(const AccessRightsElements & elements) const;
template <bool throw_if_denied, bool grant_option, typename... Args>
bool checkAccessImpl2(const AccessFlags & flags, const Args &... args) const;
bool checkAccessImplHelper(const AccessFlags & flags, const Args &... args) const;
template <bool throw_if_denied, bool grant_option>
bool checkAccessImplHelper(const AccessRightsElement & element) const;
template <bool throw_if_denied>
bool checkAdminOptionImpl(const UUID & role_id) const;
@ -201,7 +206,7 @@ private:
bool checkAdminOptionImpl(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const;
template <bool throw_if_denied, typename Container, typename GetNameFunction>
bool checkAdminOptionImpl2(const Container & role_ids, const GetNameFunction & get_name_function) const;
bool checkAdminOptionImplHelper(const Container & role_ids, const GetNameFunction & get_name_function) const;
const AccessControlManager * manager = nullptr;
const Params params;

View File

@ -1,37 +1,38 @@
#include <Access/GrantedRoles.h>
#include <Access/RolesOrUsersSet.h>
#include <boost/range/algorithm/set_algorithm.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
namespace DB
{
void GrantedRoles::grant(const UUID & role)
void GrantedRoles::grant(const UUID & role_)
{
roles.insert(role);
roles.insert(role_);
}
void GrantedRoles::grant(const std::vector<UUID> & roles_)
{
for (const UUID & role : roles_)
grant(role);
roles.insert(roles_.begin(), roles_.end());
}
void GrantedRoles::grantWithAdminOption(const UUID & role)
void GrantedRoles::grantWithAdminOption(const UUID & role_)
{
roles.insert(role);
roles_with_admin_option.insert(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);
roles.insert(roles_.begin(), roles_.end());
roles_with_admin_option.insert(roles_.begin(), roles_.end());
}
void GrantedRoles::revoke(const UUID & role)
void GrantedRoles::revoke(const UUID & role_)
{
roles.erase(role);
roles_with_admin_option.erase(role);
roles.erase(role_);
roles_with_admin_option.erase(role_);
}
void GrantedRoles::revoke(const std::vector<UUID> & roles_)
@ -40,9 +41,9 @@ void GrantedRoles::revoke(const std::vector<UUID> & roles_)
revoke(role);
}
void GrantedRoles::revokeAdminOption(const UUID & role)
void GrantedRoles::revokeAdminOption(const UUID & role_)
{
roles_with_admin_option.erase(role);
roles_with_admin_option.erase(role_);
}
void GrantedRoles::revokeAdminOption(const std::vector<UUID> & roles_)
@ -52,13 +53,118 @@ void GrantedRoles::revokeAdminOption(const std::vector<UUID> & roles_)
}
GrantedRoles::Grants GrantedRoles::getGrants() const
bool GrantedRoles::isGranted(const UUID & role_) 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 roles.count(role_);
}
bool GrantedRoles::isGrantedWithAdminOption(const UUID & role_) const
{
return roles_with_admin_option.count(role_);
}
std::vector<UUID> GrantedRoles::findGranted(const std::vector<UUID> & ids) const
{
std::vector<UUID> res;
res.reserve(ids.size());
for (const UUID & id : ids)
{
if (isGranted(id))
res.push_back(id);
}
return res;
}
std::vector<UUID> GrantedRoles::findGranted(const boost::container::flat_set<UUID> & ids) const
{
std::vector<UUID> res;
res.reserve(ids.size());
boost::range::set_difference(ids, roles, std::back_inserter(res));
return res;
}
std::vector<UUID> GrantedRoles::findGranted(const RolesOrUsersSet & ids) const
{
std::vector<UUID> res;
for (const UUID & id : roles)
{
if (ids.match(id))
res.emplace_back(id);
}
return res;
}
std::vector<UUID> GrantedRoles::findGrantedWithAdminOption(const std::vector<UUID> & ids) const
{
std::vector<UUID> res;
res.reserve(ids.size());
for (const UUID & id : ids)
{
if (isGrantedWithAdminOption(id))
res.push_back(id);
}
return res;
}
std::vector<UUID> GrantedRoles::findGrantedWithAdminOption(const boost::container::flat_set<UUID> & ids) const
{
std::vector<UUID> res;
res.reserve(ids.size());
boost::range::set_difference(ids, roles_with_admin_option, std::back_inserter(res));
return res;
}
std::vector<UUID> GrantedRoles::findGrantedWithAdminOption(const RolesOrUsersSet & ids) const
{
std::vector<UUID> res;
for (const UUID & id : roles_with_admin_option)
{
if (ids.match(id))
res.emplace_back(id);
}
return res;
}
GrantedRoles::Elements GrantedRoles::getElements() const
{
Elements elements;
Element element;
element.ids.reserve(roles.size());
boost::range::set_difference(roles, roles_with_admin_option, std::back_inserter(element.ids));
if (!element.empty())
{
element.admin_option = false;
elements.emplace_back(std::move(element));
}
if (!roles_with_admin_option.empty())
{
element = {};
element.ids.insert(element.ids.end(), roles_with_admin_option.begin(), roles_with_admin_option.end());
element.admin_option = true;
elements.emplace_back(std::move(element));
}
return elements;
}
void GrantedRoles::makeUnion(const GrantedRoles & other)
{
roles.insert(other.roles.begin(), other.roles.end());
roles_with_admin_option.insert(other.roles_with_admin_option.begin(), other.roles_with_admin_option.end());
}
void GrantedRoles::makeIntersection(const GrantedRoles & other)
{
boost::range::remove_erase_if(roles, [&other](const UUID & id) { return other.roles.find(id) == other.roles.end(); });
boost::range::remove_erase_if(roles_with_admin_option, [&other](const UUID & id)
{
return other.roles_with_admin_option.find(id) == other.roles_with_admin_option.end();
});
}
}

View File

@ -7,33 +7,55 @@
namespace DB
{
struct RolesOrUsersSet;
/// Roles when they are granted to a role or user.
/// Stores both the roles themselves and the roles with admin option.
struct GrantedRoles
class GrantedRoles
{
boost::container::flat_set<UUID> roles;
boost::container::flat_set<UUID> roles_with_admin_option;
void grant(const UUID & role);
public:
void grant(const UUID & role_);
void grant(const std::vector<UUID> & roles_);
void grantWithAdminOption(const UUID & role);
void grantWithAdminOption(const UUID & role_);
void grantWithAdminOption(const std::vector<UUID> & roles_);
void revoke(const UUID & role);
void revoke(const UUID & role_);
void revoke(const std::vector<UUID> & roles_);
void revokeAdminOption(const UUID & role);
void revokeAdminOption(const UUID & role_);
void revokeAdminOption(const std::vector<UUID> & roles_);
struct Grants
bool isGranted(const UUID & role_) const;
bool isGrantedWithAdminOption(const UUID & role_) const;
const boost::container::flat_set<UUID> & getGranted() const { return roles; }
const boost::container::flat_set<UUID> & getGrantedWithAdminOption() const { return roles_with_admin_option; }
std::vector<UUID> findGranted(const std::vector<UUID> & ids) const;
std::vector<UUID> findGranted(const boost::container::flat_set<UUID> & ids) const;
std::vector<UUID> findGranted(const RolesOrUsersSet & ids) const;
std::vector<UUID> findGrantedWithAdminOption(const std::vector<UUID> & ids) const;
std::vector<UUID> findGrantedWithAdminOption(const boost::container::flat_set<UUID> & ids) const;
std::vector<UUID> findGrantedWithAdminOption(const RolesOrUsersSet & ids) const;
struct Element
{
std::vector<UUID> grants;
std::vector<UUID> grants_with_admin_option;
std::vector<UUID> ids;
bool admin_option = false;
bool empty() const { return ids.empty(); }
};
using Elements = std::vector<Element>;
/// Retrieves the information about grants.
Grants getGrants() const;
Elements getElements() const;
void makeUnion(const GrantedRoles & other);
void makeIntersection(const GrantedRoles & other);
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); }
private:
boost::container::flat_set<UUID> roles;
boost::container::flat_set<UUID> roles_with_admin_option;
};
}

View File

@ -187,13 +187,10 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id,
if (auto user = typeid_cast<std::shared_ptr<const User>>(entity_))
{
auto changed_user = typeid_cast<std::shared_ptr<User>>(user->clone());
auto & granted_roles = changed_user->granted_roles.roles;
if (grant)
granted_roles.insert(role_id);
changed_user->granted_roles.grant(role_id);
else
granted_roles.erase(role_id);
changed_user->granted_roles.revoke(role_id);
return changed_user;
}
return entity_;
@ -229,7 +226,7 @@ void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPClient::SearchR
void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPClient::SearchResultsList & external_roles, const std::size_t external_roles_hash) const
{
const auto & user_name = user.getName();
auto & granted_roles = user.granted_roles.roles;
auto & granted_roles = user.granted_roles;
const auto local_role_names = mapExternalRolesNoLock(external_roles);
auto grant_role = [this, &user_name, &granted_roles] (const String & role_name, const bool common)
@ -247,7 +244,7 @@ void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPClient::SearchR
if (it != granted_role_ids.end())
{
const auto & role_id = it->second;
granted_roles.insert(role_id);
granted_roles.grant(role_id);
}
else
{
@ -256,7 +253,7 @@ void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPClient::SearchR
};
external_role_hashes.erase(user_name);
granted_roles.clear();
granted_roles = {};
const auto old_role_names = std::move(roles_per_users[user_name]);
// Grant the common roles first.

View File

@ -46,10 +46,10 @@ namespace
roles_info.access.makeUnion(role->access);
roles_info.settings_from_enabled_roles.merge(role->settings);
for (const auto & granted_role : role->granted_roles.roles)
for (const auto & granted_role : role->granted_roles.getGranted())
collectRoles(roles_info, skip_ids, get_role_function, granted_role, false, false);
for (const auto & granted_role : role->granted_roles.roles_with_admin_option)
for (const auto & granted_role : role->granted_roles.getGrantedWithAdminOption())
collectRoles(roles_info, skip_ids, get_role_function, granted_role, false, true);
}
}
@ -63,15 +63,15 @@ RoleCache::~RoleCache() = default;
std::shared_ptr<const EnabledRoles>
RoleCache::getEnabledRoles(const boost::container::flat_set<UUID> & roles, const boost::container::flat_set<UUID> & roles_with_admin_option)
RoleCache::getEnabledRoles(const std::vector<UUID> & roles, const std::vector<UUID> & roles_with_admin_option)
{
/// Declared before `lock` to send notifications after the mutex will be unlocked.
ext::scope_guard notifications;
std::lock_guard lock{mutex};
EnabledRoles::Params params;
params.current_roles = roles;
params.current_roles_with_admin_option = roles_with_admin_option;
params.current_roles.insert(roles.begin(), roles.end());
params.current_roles_with_admin_option.insert(roles_with_admin_option.begin(), roles_with_admin_option.end());
auto it = enabled_roles.find(params);
if (it != enabled_roles.end())
{

View File

@ -20,7 +20,8 @@ public:
~RoleCache();
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 std::vector<UUID> & current_roles,
const std::vector<UUID> & current_roles_with_admin_option);
private:
void collectEnabledRoles(ext::scope_guard & notifications);

View File

@ -72,20 +72,20 @@ void RolesOrUsersSet::init(const ASTRolesOrUsersSet & ast, const AccessControlMa
if (ast.id_mode)
return parse<UUID>(name);
assert(manager);
if (ast.allow_user_names && ast.allow_role_names)
if (ast.allow_users && ast.allow_roles)
{
auto id = manager->find<User>(name);
if (id)
return *id;
return manager->getID<Role>(name);
}
else if (ast.allow_user_names)
else if (ast.allow_users)
{
return manager->getID<User>(name);
}
else
{
assert(ast.allow_role_names);
assert(ast.allow_roles);
return manager->getID<Role>(name);
}
};
@ -106,8 +106,8 @@ void RolesOrUsersSet::init(const ASTRolesOrUsersSet & ast, const AccessControlMa
if (!ast.except_names.empty())
{
except_ids.reserve(ast.except_names.size());
for (const String & except_name : ast.except_names)
except_ids.insert(name_to_id(except_name));
for (const String & name : ast.except_names)
except_ids.insert(name_to_id(name));
}
if (ast.except_current_user)
@ -116,8 +116,8 @@ void RolesOrUsersSet::init(const ASTRolesOrUsersSet & ast, const AccessControlMa
except_ids.insert(*current_user_id);
}
for (const UUID & except_id : except_ids)
ids.erase(except_id);
for (const UUID & id : except_ids)
ids.erase(id);
}
@ -127,7 +127,7 @@ std::shared_ptr<ASTRolesOrUsersSet> RolesOrUsersSet::toAST() const
ast->id_mode = true;
ast->all = all;
if (!ids.empty())
if (!ids.empty() && !all)
{
ast->names.reserve(ids.size());
for (const UUID & id : ids)
@ -152,7 +152,7 @@ std::shared_ptr<ASTRolesOrUsersSet> RolesOrUsersSet::toASTWithNames(const Access
auto ast = std::make_shared<ASTRolesOrUsersSet>();
ast->all = all;
if (!ids.empty())
if (!ids.empty() && !all)
{
ast->names.reserve(ids.size());
for (const UUID & id : ids)
@ -194,44 +194,6 @@ String RolesOrUsersSet::toStringWithNames(const AccessControlManager & manager)
}
Strings RolesOrUsersSet::toStringsWithNames(const AccessControlManager & manager) const
{
if (!all && ids.empty())
return {};
Strings res;
res.reserve(ids.size() + except_ids.size());
if (all)
res.emplace_back("ALL");
else
{
for (const UUID & id : ids)
{
auto name = manager.tryReadName(id);
if (name)
res.emplace_back(std::move(*name));
}
std::sort(res.begin(), res.end());
}
if (!except_ids.empty())
{
res.emplace_back("EXCEPT");
size_t old_size = res.size();
for (const UUID & id : except_ids)
{
auto name = manager.tryReadName(id);
if (name)
res.emplace_back(std::move(*name));
}
std::sort(res.begin() + old_size, res.end());
}
return res;
}
bool RolesOrUsersSet::empty() const
{
return ids.empty() && !all;
@ -248,14 +210,18 @@ void RolesOrUsersSet::clear()
void RolesOrUsersSet::add(const UUID & id)
{
if (!all)
ids.insert(id);
except_ids.erase(id);
}
void RolesOrUsersSet::add(const std::vector<UUID> & ids_)
{
if (!all)
ids.insert(ids_.begin(), ids_.end());
for (const auto & id : ids_)
add(id);
except_ids.erase(id);
}

View File

@ -13,7 +13,8 @@ class AccessControlManager;
/// Represents a set of users/roles like
/// {user_name | role_name | CURRENT_USER} [,...] | NONE | ALL | ALL EXCEPT {user_name | role_name | CURRENT_USER} [,...]
/// {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]
/// [EXCEPT {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]]
/// Similar to ASTRolesOrUsersSet, but with IDs instead of names.
struct RolesOrUsersSet
{
@ -60,8 +61,8 @@ struct RolesOrUsersSet
friend bool operator ==(const RolesOrUsersSet & lhs, const RolesOrUsersSet & rhs);
friend bool operator !=(const RolesOrUsersSet & lhs, const RolesOrUsersSet & rhs) { return !(lhs == rhs); }
boost::container::flat_set<UUID> ids;
bool all = false;
boost::container::flat_set<UUID> ids;
boost::container::flat_set<UUID> except_ids;
private:

View File

@ -753,7 +753,7 @@ std::optional<UUID> Context::getUserID() const
}
void Context::setCurrentRoles(const boost::container::flat_set<UUID> & current_roles_)
void Context::setCurrentRoles(const std::vector<UUID> & current_roles_)
{
auto lock = getLock();
if (current_roles == current_roles_ && !use_default_roles)

View File

@ -181,7 +181,7 @@ private:
InputBlocksReader input_blocks_reader;
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
std::vector<UUID> current_roles;
bool use_default_roles = false;
std::shared_ptr<const ContextAccess> access;
std::shared_ptr<const EnabledRowPolicies> initial_row_policy;
@ -354,7 +354,7 @@ public:
String getUserName() const;
std::optional<UUID> getUserID() const;
void setCurrentRoles(const boost::container::flat_set<UUID> & current_roles_);
void setCurrentRoles(const std::vector<UUID> & current_roles_);
void setCurrentRolesDefault();
boost::container::flat_set<UUID> getCurrentRoles() const;
boost::container::flat_set<UUID> getEnabledRoles() const;

View File

@ -78,7 +78,7 @@ BlockIO InterpreterCreateQuotaQuery::execute()
if (!query.cluster.empty())
{
query.replaceCurrentUserTagWithName(context.getUserName());
query.replaceCurrentUserTag(context.getUserName());
return executeDDLQueryOnCluster(query_ptr, context);
}

View File

@ -49,7 +49,7 @@ BlockIO InterpreterCreateRowPolicyQuery::execute()
if (!query.cluster.empty())
{
query.replaceCurrentUserTagWithName(context.getUserName());
query.replaceCurrentUserTag(context.getUserName());
return executeDDLQueryOnCluster(query_ptr, context);
}
@ -58,7 +58,7 @@ BlockIO InterpreterCreateRowPolicyQuery::execute()
if (query.roles)
roles_from_query = RolesOrUsersSet{*query.roles, access_control, context.getUserID()};
query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
query.replaceEmptyDatabase(context.getCurrentDatabase());
if (query.alter)
{

View File

@ -50,7 +50,7 @@ BlockIO InterpreterCreateSettingsProfileQuery::execute()
if (!query.cluster.empty())
{
query.replaceCurrentUserTagWithName(context.getUserName());
query.replaceCurrentUserTag(context.getUserName());
return executeDDLQueryOnCluster(query_ptr, context);
}

View File

@ -31,7 +31,7 @@ BlockIO InterpreterDropAccessEntityQuery::execute()
if (!query.cluster.empty())
return executeDDLQueryOnCluster(query_ptr, context);
query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
query.replaceEmptyDatabase(context.getCurrentDatabase());
auto do_drop = [&](const Strings & names)
{

View File

@ -12,13 +12,15 @@
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>
namespace DB
{
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
}
namespace
{
using Kind = ASTGrantQuery::Kind;
template <typename T>
void updateFromQueryTemplate(
T & grantee,
@ -27,38 +29,28 @@ namespace
{
if (!query.access_rights_elements.empty())
{
if (query.kind == Kind::GRANT)
{
if (query.grant_option)
grantee.access.grantWithGrantOption(query.access_rights_elements);
if (query.is_revoke)
grantee.access.revoke(query.access_rights_elements);
else
grantee.access.grant(query.access_rights_elements);
}
else
{
if (query.grant_option)
grantee.access.revokeGrantOption(query.access_rights_elements);
else
grantee.access.revoke(query.access_rights_elements);
}
}
if (!roles_to_grant_or_revoke.empty())
{
if (query.kind == Kind::GRANT)
{
if (query.admin_option)
grantee.granted_roles.grantWithAdminOption(roles_to_grant_or_revoke);
else
grantee.granted_roles.grant(roles_to_grant_or_revoke);
}
else
if (query.is_revoke)
{
if (query.admin_option)
grantee.granted_roles.revokeAdminOption(roles_to_grant_or_revoke);
else
grantee.granted_roles.revoke(roles_to_grant_or_revoke);
}
else
{
if (query.admin_option)
grantee.granted_roles.grantWithAdminOption(roles_to_grant_or_revoke);
else
grantee.granted_roles.grant(roles_to_grant_or_revoke);
}
}
}
@ -72,122 +64,166 @@ namespace
else if (auto * role = typeid_cast<Role *>(&grantee))
updateFromQueryTemplate(*role, query, roles_to_grant_or_revoke);
}
void checkGrantOption(
const AccessControlManager & access_control,
const ContextAccess & access,
const ASTGrantQuery & query,
const std::vector<UUID> & grantees_from_query)
{
const auto & elements = query.access_rights_elements;
if (elements.empty())
return;
/// To execute the command GRANT the current user needs to have the access granted
/// with GRANT OPTION.
if (!query.is_revoke)
{
access.checkGrantOption(elements);
return;
}
if (access.hasGrantOption(elements))
return;
/// Special case for the command REVOKE: it's possible that the current user doesn't have
/// the access granted with GRANT OPTION but it's still ok because the roles or users
/// from whom the access rights will be revoked don't have the specified access granted either.
///
/// For example, to execute
/// GRANT ALL ON mydb.* TO role1
/// REVOKE ALL ON *.* FROM role1
/// the current user needs to have grants only on the 'mydb' database.
AccessRights all_granted_access;
for (const auto & id : grantees_from_query)
{
auto entity = access_control.tryRead(id);
if (auto role = typeid_cast<RolePtr>(entity))
all_granted_access.makeUnion(role->access);
else if (auto user = typeid_cast<UserPtr>(entity))
all_granted_access.makeUnion(user->access);
}
AccessRights required_access;
if (elements[0].is_partial_revoke)
{
AccessRightsElements non_revoke_elements = elements;
std::for_each(non_revoke_elements.begin(), non_revoke_elements.end(), [&](AccessRightsElement & element) { element.is_partial_revoke = false; });
required_access.grant(non_revoke_elements);
}
else
{
required_access.grant(elements);
}
required_access.makeIntersection(all_granted_access);
for (auto & required_access_element : required_access.getElements())
{
if (!required_access_element.is_partial_revoke && (required_access_element.grant_option || !elements[0].grant_option))
access.checkGrantOption(required_access_element);
}
}
std::vector<UUID> getRoleIDsAndCheckAdminOption(
const AccessControlManager & access_control,
const ContextAccess & access,
const ASTGrantQuery & query,
const RolesOrUsersSet & roles_from_query,
const std::vector<UUID> & grantees_from_query)
{
std::vector<UUID> matching_ids;
if (!query.is_revoke)
{
matching_ids = roles_from_query.getMatchingIDs(access_control);
access.checkAdminOption(matching_ids);
return matching_ids;
}
if (!roles_from_query.all)
{
matching_ids = roles_from_query.getMatchingIDs();
if (access.hasAdminOption(matching_ids))
return matching_ids;
}
/// Special case for the command REVOKE: it's possible that the current user doesn't have the admin option
/// for some of the specified roles but it's still ok because the roles or users from whom the roles will be
/// revoked from don't have the specified roles granted either.
///
/// For example, to execute
/// GRANT role2 TO role1
/// REVOKE ALL FROM role1
/// the current user needs to have only 'role2' to be granted with admin option (not all the roles).
GrantedRoles all_granted_roles;
for (const auto & id : grantees_from_query)
{
auto entity = access_control.tryRead(id);
if (auto role = typeid_cast<RolePtr>(entity))
all_granted_roles.makeUnion(role->granted_roles);
else if (auto user = typeid_cast<UserPtr>(entity))
all_granted_roles.makeUnion(user->granted_roles);
}
const auto & all_granted_roles_set = query.admin_option ? all_granted_roles.getGrantedWithAdminOption() : all_granted_roles.getGranted();
if (roles_from_query.all)
boost::range::set_difference(all_granted_roles_set, roles_from_query.except_ids, std::back_inserter(matching_ids));
else
boost::range::remove_erase_if(matching_ids, [&](const UUID & id) { return !all_granted_roles_set.count(id); });
access.checkAdminOption(matching_ids);
return matching_ids;
}
}
BlockIO InterpreterGrantQuery::execute()
{
auto & query = query_ptr->as<ASTGrantQuery &>();
query.replaceCurrentUserTagWithName(context.getUserName());
if (!query.cluster.empty())
return executeDDLQueryOnCluster(query_ptr, context, query.access_rights_elements, true);
query.replaceCurrentUserTag(context.getUserName());
query.access_rights_elements.eraseNonGrantable();
if (!query.access_rights_elements.sameOptions())
throw Exception("Elements of an ASTGrantQuery are expected to have the same options", ErrorCodes::LOGICAL_ERROR);
if (!query.access_rights_elements.empty() && query.access_rights_elements[0].is_partial_revoke && !query.is_revoke)
throw Exception("A partial revoke should be revoked, not granted", ErrorCodes::LOGICAL_ERROR);
auto access = context.getAccess();
auto & access_control = context.getAccessControlManager();
query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
RolesOrUsersSet roles_set;
std::optional<RolesOrUsersSet> roles_set;
if (query.roles)
roles_set = RolesOrUsersSet{*query.roles, access_control};
std::vector<UUID> to_roles = RolesOrUsersSet{*query.to_roles, access_control, context.getUserID()}.getMatchingIDs(access_control);
std::vector<UUID> grantees = RolesOrUsersSet{*query.grantees, access_control, context.getUserID()}.getMatchingIDs(access_control);
/// Check if the current user has corresponding roles granted with admin option.
std::vector<UUID> roles;
if (roles_set)
roles = getRoleIDsAndCheckAdminOption(access_control, *context.getAccess(), query, *roles_set, grantees);
if (!query.cluster.empty())
{
/// To execute the command GRANT the current user needs to have the access granted with GRANT OPTION.
auto required_access = query.access_rights_elements;
std::for_each(required_access.begin(), required_access.end(), [&](AccessRightsElement & element) { element.grant_option = true; });
return executeDDLQueryOnCluster(query_ptr, context, std::move(required_access));
}
query.replaceEmptyDatabase(context.getCurrentDatabase());
/// Check if the current user has corresponding access rights with grant option.
if (!query.access_rights_elements.empty())
{
query.access_rights_elements.removeNonGrantableFlags();
checkGrantOption(access_control, *context.getAccess(), query, grantees);
/// Special case for REVOKE: it's possible that the current user doesn't have the grant option for all
/// the specified access rights and that's ok because the roles or users which the access rights
/// will be revoked from don't have the specified access rights either.
///
/// For example, to execute
/// GRANT ALL ON mydb.* TO role1
/// REVOKE ALL ON *.* FROM role1
/// the current user needs to have access rights only for the 'mydb' database.
if ((query.kind == Kind::REVOKE) && !access->hasGrantOption(query.access_rights_elements))
{
AccessRights max_access;
for (const auto & id : to_roles)
{
auto entity = access_control.tryRead(id);
if (auto role = typeid_cast<RolePtr>(entity))
max_access.makeUnion(role->access);
else if (auto user = typeid_cast<UserPtr>(entity))
max_access.makeUnion(user->access);
}
AccessRights access_to_revoke;
if (query.grant_option)
access_to_revoke.grantWithGrantOption(query.access_rights_elements);
else
access_to_revoke.grant(query.access_rights_elements);
access_to_revoke.makeIntersection(max_access);
AccessRightsElements filtered_access_to_revoke;
for (auto & element : access_to_revoke.getElements())
{
if ((element.kind == Kind::GRANT) && (element.grant_option || !query.grant_option))
filtered_access_to_revoke.emplace_back(std::move(element));
}
query.access_rights_elements = std::move(filtered_access_to_revoke);
}
access->checkGrantOption(query.access_rights_elements);
}
/// Check if the current user has corresponding roles granted with admin option.
std::vector<UUID> roles_to_grant_or_revoke;
if (!roles_set.empty())
{
bool all = roles_set.all;
if (!all)
roles_to_grant_or_revoke = roles_set.getMatchingIDs();
/// Special case for REVOKE: it's possible that the current user doesn't have the admin option for all
/// the specified roles and that's ok because the roles or users which the roles will be revoked from
/// don't have the specified roles granted either.
///
/// For example, to execute
/// GRANT role2 TO role1
/// REVOKE ALL FROM role1
/// the current user needs to have only 'role2' to be granted with admin option (not all the roles).
if ((query.kind == Kind::REVOKE) && (roles_set.all || !access->hasAdminOption(roles_to_grant_or_revoke)))
{
auto & roles_to_revoke = roles_to_grant_or_revoke;
boost::container::flat_set<UUID> max_roles;
for (const auto & id : to_roles)
{
auto entity = access_control.tryRead(id);
auto add_to_max_roles = [&](const GrantedRoles & granted_roles)
{
if (query.admin_option)
max_roles.insert(granted_roles.roles_with_admin_option.begin(), granted_roles.roles_with_admin_option.end());
else
max_roles.insert(granted_roles.roles.begin(), granted_roles.roles.end());
};
if (auto role = typeid_cast<RolePtr>(entity))
add_to_max_roles(role->granted_roles);
else if (auto user = typeid_cast<UserPtr>(entity))
add_to_max_roles(user->granted_roles);
}
if (roles_set.all)
boost::range::set_difference(max_roles, roles_set.except_ids, std::back_inserter(roles_to_revoke));
else
boost::range::remove_erase_if(roles_to_revoke, [&](const UUID & id) { return !max_roles.count(id); });
}
access->checkAdminOption(roles_to_grant_or_revoke);
}
/// Update roles and users listed in `to_roles`.
/// Update roles and users listed in `grantees`.
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
{
auto clone = entity->clone();
updateFromQueryImpl(*clone, query, roles_to_grant_or_revoke);
updateFromQueryImpl(*clone, query, roles);
return clone;
};
access_control.update(to_roles, update_func);
access_control.update(grantees, update_func);
return {};
}
@ -213,10 +249,10 @@ void InterpreterGrantQuery::updateRoleFromQuery(Role & role, const ASTGrantQuery
void InterpreterGrantQuery::extendQueryLogElemImpl(QueryLogElement & elem, const ASTPtr & /*ast*/, const Context &) const
{
auto & query = query_ptr->as<ASTGrantQuery &>();
if (query.kind == Kind::GRANT)
elem.query_kind = "Grant";
else if (query.kind == Kind::REVOKE)
if (query.is_revoke)
elem.query_kind = "Revoke";
else
elem.query_kind = "Grant";
}
}

View File

@ -39,20 +39,18 @@ void InterpreterSetRoleQuery::setRole(const ASTSetRoleQuery & query)
else
{
RolesOrUsersSet roles_from_query{*query.roles, access_control};
boost::container::flat_set<UUID> new_current_roles;
std::vector<UUID> new_current_roles;
if (roles_from_query.all)
{
for (const auto & id : user->granted_roles.roles)
if (roles_from_query.match(id))
new_current_roles.emplace(id);
new_current_roles = user->granted_roles.findGranted(roles_from_query);
}
else
{
for (const auto & id : roles_from_query.getMatchingIDs())
{
if (!user->granted_roles.roles.count(id))
if (!user->granted_roles.isGranted(id))
throw Exception("Role should be granted to set current", ErrorCodes::SET_NON_GRANTED_ROLE);
new_current_roles.emplace(id);
new_current_roles.emplace_back(id);
}
}
session_context.setCurrentRoles(new_current_roles);
@ -85,7 +83,7 @@ void InterpreterSetRoleQuery::updateUserSetDefaultRoles(User & user, const Roles
{
for (const auto & id : roles_from_query.getMatchingIDs())
{
if (!user.granted_roles.roles.count(id))
if (!user.granted_roles.isGranted(id))
throw Exception("Role should be granted to set default", ErrorCodes::SET_NON_GRANTED_ROLE);
}
}

View File

@ -32,7 +32,7 @@ BlockIO InterpreterShowAccessEntitiesQuery::execute()
String InterpreterShowAccessEntitiesQuery::getRewrittenQuery() const
{
auto & query = query_ptr->as<ASTShowAccessEntitiesQuery &>();
query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
query.replaceEmptyDatabase(context.getCurrentDatabase());
String origin;
String expr = "*";
String filter, order;

View File

@ -263,7 +263,7 @@ std::vector<AccessEntityPtr> InterpreterShowCreateAccessEntityQuery::getEntities
auto & show_query = query_ptr->as<ASTShowCreateAccessEntityQuery &>();
const auto & access_control = context.getAccessControlManager();
context.checkAccess(getRequiredAccess());
show_query.replaceEmptyDatabaseWithCurrent(context.getCurrentDatabase());
show_query.replaceEmptyDatabase(context.getCurrentDatabase());
std::vector<AccessEntityPtr> entities;
if (show_query.all)

View File

@ -32,56 +32,50 @@ namespace
{
ASTs res;
std::shared_ptr<ASTRolesOrUsersSet> to_roles = std::make_shared<ASTRolesOrUsersSet>();
to_roles->names.push_back(grantee.getName());
std::shared_ptr<ASTRolesOrUsersSet> grantees = std::make_shared<ASTRolesOrUsersSet>();
grantees->names.push_back(grantee.getName());
std::shared_ptr<ASTGrantQuery> current_query = nullptr;
auto elements = grantee.access.getElements();
for (const auto & element : elements)
for (const auto & element : grantee.access.getElements())
{
if (element.empty())
continue;
if (current_query)
{
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)
bool continue_with_current_query = element.sameDatabaseAndTable(prev_element) && element.sameOptions(prev_element);
if (!continue_with_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;
current_query->grantees = grantees;
current_query->attach_mode = attach_mode;
if (element.is_partial_revoke)
current_query->is_revoke = true;
res.push_back(current_query);
}
current_query->access_rights_elements.emplace_back(std::move(element));
}
auto grants_roles = grantee.granted_roles.getGrants();
for (bool admin_option : {false, true})
for (const auto & element : grantee.granted_roles.getElements())
{
const auto & roles = admin_option ? grants_roles.grants_with_admin_option : grants_roles.grants;
if (roles.empty())
if (element.empty())
continue;
auto grant_query = std::make_shared<ASTGrantQuery>();
using Kind = ASTGrantQuery::Kind;
grant_query->kind = Kind::GRANT;
grant_query->attach = attach_mode;
grant_query->admin_option = admin_option;
grant_query->to_roles = to_roles;
grant_query->grantees = grantees;
grant_query->admin_option = element.admin_option;
grant_query->attach_mode = attach_mode;
if (attach_mode)
grant_query->roles = RolesOrUsersSet{roles}.toAST();
grant_query->roles = RolesOrUsersSet{element.ids}.toAST();
else
grant_query->roles = RolesOrUsersSet{roles}.toASTWithNames(*manager);
grant_query->roles = RolesOrUsersSet{element.ids}.toASTWithNames(*manager);
res.push_back(std::move(grant_query));
}

View File

@ -50,12 +50,12 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & cont
return executeDDLQueryOnCluster(query_ptr_, context, {});
}
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, const AccessRightsElements & query_requires_access, bool query_requires_grant_option)
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, const AccessRightsElements & query_requires_access)
{
return executeDDLQueryOnCluster(query_ptr, context, AccessRightsElements{query_requires_access}, query_requires_grant_option);
return executeDDLQueryOnCluster(query_ptr, context, AccessRightsElements{query_requires_access});
}
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & context, AccessRightsElements && query_requires_access, bool query_requires_grant_option)
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & context, AccessRightsElements && query_requires_access)
{
/// Remove FORMAT <fmt> and INTO OUTFILE <file> if exists
ASTPtr query_ptr = query_ptr_->clone();
@ -154,9 +154,6 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr_, const Context & cont
visitor.visitDDL(query_ptr);
/// Check access rights, assume that all servers have the same users config
if (query_requires_grant_option)
context.getAccess()->checkGrantOption(query_requires_access);
else
context.checkAccess(query_requires_access);
DDLLogEntry entry;

View File

@ -21,8 +21,8 @@ bool isSupportedAlterType(int type);
/// Pushes distributed DDL query to the queue.
/// Returns DDLQueryStatusInputStream, which reads results of query execution on each host in the cluster.
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);
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, const AccessRightsElements & query_requires_access);
BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, const Context & context, AccessRightsElements && query_requires_access);
class DDLQueryStatusInputStream final : public IBlockInputStream

View File

@ -185,10 +185,10 @@ void ASTCreateQuotaQuery::formatImpl(const FormatSettings & settings, FormatStat
}
void ASTCreateQuotaQuery::replaceCurrentUserTagWithName(const String & current_user_name) const
void ASTCreateQuotaQuery::replaceCurrentUserTag(const String & current_user_name) const
{
if (roles)
roles->replaceCurrentUserTagWithName(current_user_name);
roles->replaceCurrentUserTag(current_user_name);
}
}

View File

@ -56,7 +56,7 @@ public:
String getID(char) const override;
ASTPtr clone() const override;
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
void replaceCurrentUserTagWithName(const String & current_user_name) const;
void replaceCurrentUserTag(const String & current_user_name) const;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTCreateQuotaQuery>(clone()); }
};
}

View File

@ -169,15 +169,15 @@ void ASTCreateRowPolicyQuery::formatImpl(const FormatSettings & settings, Format
}
void ASTCreateRowPolicyQuery::replaceCurrentUserTagWithName(const String & current_user_name) const
void ASTCreateRowPolicyQuery::replaceCurrentUserTag(const String & current_user_name) const
{
if (roles)
roles->replaceCurrentUserTagWithName(current_user_name);
roles->replaceCurrentUserTag(current_user_name);
}
void ASTCreateRowPolicyQuery::replaceEmptyDatabaseWithCurrent(const String & current_database) const
void ASTCreateRowPolicyQuery::replaceEmptyDatabase(const String & current_database) const
{
if (names)
names->replaceEmptyDatabaseWithCurrent(current_database);
names->replaceEmptyDatabase(current_database);
}
}

View File

@ -49,7 +49,7 @@ public:
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTCreateRowPolicyQuery>(clone()); }
void replaceCurrentUserTagWithName(const String & current_user_name) const;
void replaceEmptyDatabaseWithCurrent(const String & current_database) const;
void replaceCurrentUserTag(const String & current_user_name) const;
void replaceEmptyDatabase(const String & current_database) const;
};
}

View File

@ -86,9 +86,9 @@ void ASTCreateSettingsProfileQuery::formatImpl(const FormatSettings & format, Fo
}
void ASTCreateSettingsProfileQuery::replaceCurrentUserTagWithName(const String & current_user_name) const
void ASTCreateSettingsProfileQuery::replaceCurrentUserTag(const String & current_user_name) const
{
if (to_roles)
to_roles->replaceCurrentUserTagWithName(current_user_name);
to_roles->replaceCurrentUserTag(current_user_name);
}
}

View File

@ -39,7 +39,7 @@ public:
String getID(char) const override;
ASTPtr clone() const override;
void formatImpl(const FormatSettings & format, FormatState &, FormatStateStacked) const override;
void replaceCurrentUserTagWithName(const String & current_user_name) const;
void replaceCurrentUserTag(const String & current_user_name) const;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTCreateSettingsProfileQuery>(clone()); }
};
}

View File

@ -46,7 +46,6 @@ public:
std::optional<AllowedClientHosts> remove_hosts;
std::shared_ptr<ASTRolesOrUsersSet> default_roles;
std::shared_ptr<ASTSettingsProfileElements> settings;
String getID(char) const override;

View File

@ -54,9 +54,9 @@ void ASTDropAccessEntityQuery::formatImpl(const FormatSettings & settings, Forma
}
void ASTDropAccessEntityQuery::replaceEmptyDatabaseWithCurrent(const String & current_database) const
void ASTDropAccessEntityQuery::replaceEmptyDatabase(const String & current_database) const
{
if (row_policy_names)
row_policy_names->replaceEmptyDatabaseWithCurrent(current_database);
row_policy_names->replaceEmptyDatabase(current_database);
}
}

View File

@ -30,6 +30,6 @@ public:
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTDropAccessEntityQuery>(clone()); }
void replaceEmptyDatabaseWithCurrent(const String & current_database) const;
void replaceEmptyDatabase(const String & current_database) const;
};
}

View File

@ -27,7 +27,26 @@ namespace
}
void formatAccessRightsElements(const AccessRightsElements & elements, const IAST::FormatSettings & settings)
void formatONClause(const String & database, bool any_database, const String & table, bool any_table, const IAST::FormatSettings & settings)
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "ON " << (settings.hilite ? IAST::hilite_none : "");
if (any_database)
{
settings.ostr << "*.*";
}
else
{
if (!database.empty())
settings.ostr << backQuoteIfNeed(database) << ".";
if (any_table)
settings.ostr << "*";
else
settings.ostr << backQuoteIfNeed(table);
}
}
void formatElementsWithoutOptions(const AccessRightsElements & elements, const IAST::FormatSettings & settings)
{
bool no_output = true;
for (size_t i = 0; i != elements.size(); ++i)
@ -58,31 +77,14 @@ namespace
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);
settings.ostr << " ";
formatONClause(element.database, element.any_database, element.table, element.any_table, settings);
}
}
if (no_output)
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "USAGE ON " << (settings.hilite ? IAST::hilite_none : "") << "*.*";
}
void formatToRoles(const ASTRolesOrUsersSet & to_roles, ASTGrantQuery::Kind kind, const IAST::FormatSettings & settings)
{
using Kind = ASTGrantQuery::Kind;
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << ((kind == Kind::GRANT) ? " TO " : " FROM ")
<< (settings.hilite ? IAST::hilite_none : "");
to_roles.format(settings);
}
}
@ -100,12 +102,18 @@ ASTPtr ASTGrantQuery::clone() const
void ASTGrantQuery::formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << (attach ? "ATTACH " : "") << ((kind == Kind::GRANT) ? "GRANT" : "REVOKE")
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << (attach_mode ? "ATTACH " : "") << (is_revoke ? "REVOKE" : "GRANT")
<< (settings.hilite ? IAST::hilite_none : "");
if (!access_rights_elements.sameOptions())
throw Exception("Elements of an ASTGrantQuery are expected to have the same options", ErrorCodes::LOGICAL_ERROR);
if (!access_rights_elements.empty() && access_rights_elements[0].is_partial_revoke && !is_revoke)
throw Exception("A partial revoke should be revoked, not granted", ErrorCodes::LOGICAL_ERROR);
bool grant_option = !access_rights_elements.empty() && access_rights_elements[0].grant_option;
formatOnCluster(settings);
if (kind == Kind::REVOKE)
if (is_revoke)
{
if (grant_option)
settings.ostr << (settings.hilite ? hilite_keyword : "") << " GRANT OPTION FOR" << (settings.hilite ? hilite_none : "");
@ -113,18 +121,21 @@ 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())
throw Exception("Either roles or access rights elements should be set", ErrorCodes::LOGICAL_ERROR);
settings.ostr << " ";
if (roles)
{
roles->format(settings);
if (!access_rights_elements.empty())
throw Exception("ASTGrantQuery can contain either roles or access rights elements to grant or revoke, not both of them", ErrorCodes::LOGICAL_ERROR);
}
else
formatAccessRightsElements(access_rights_elements, settings);
formatElementsWithoutOptions(access_rights_elements, settings);
formatToRoles(*to_roles, kind, settings);
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << (is_revoke ? " FROM " : " TO ")
<< (settings.hilite ? IAST::hilite_none : "");
grantees->format(settings);
if (kind == Kind::GRANT)
if (!is_revoke)
{
if (grant_option)
settings.ostr << (settings.hilite ? hilite_keyword : "") << " WITH GRANT OPTION" << (settings.hilite ? hilite_none : "");
@ -134,16 +145,16 @@ void ASTGrantQuery::formatImpl(const FormatSettings & settings, FormatState &, F
}
void ASTGrantQuery::replaceEmptyDatabaseWithCurrent(const String & current_database)
void ASTGrantQuery::replaceEmptyDatabase(const String & current_database)
{
access_rights_elements.replaceEmptyDatabase(current_database);
}
void ASTGrantQuery::replaceCurrentUserTagWithName(const String & current_user_name) const
void ASTGrantQuery::replaceCurrentUserTag(const String & current_user_name) const
{
if (to_roles)
to_roles->replaceCurrentUserTagWithName(current_user_name);
if (grantees)
grantees->replaceCurrentUserTag(current_user_name);
}
}

View File

@ -19,20 +19,18 @@ class ASTRolesOrUsersSet;
class ASTGrantQuery : public IAST, public ASTQueryWithOnCluster
{
public:
using Kind = AccessRightsElementWithOptions::Kind;
Kind kind = Kind::GRANT;
bool attach = false;
bool attach_mode = false;
bool is_revoke = false;
AccessRightsElements access_rights_elements;
std::shared_ptr<ASTRolesOrUsersSet> roles;
std::shared_ptr<ASTRolesOrUsersSet> to_roles;
bool grant_option = false;
bool admin_option = false;
std::shared_ptr<ASTRolesOrUsersSet> grantees;
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;
void replaceEmptyDatabase(const String & current_database);
void replaceCurrentUserTag(const String & current_user_name) const;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTGrantQuery>(clone()); }
};
}

View File

@ -7,7 +7,7 @@ namespace DB
{
namespace
{
void formatRoleNameOrID(const String & str, bool is_id, const IAST::FormatSettings & settings)
void formatNameOrID(const String & str, bool is_id, const IAST::FormatSettings & settings)
{
if (is_id)
{
@ -30,6 +30,7 @@ void ASTRolesOrUsersSet::formatImpl(const FormatSettings & settings, FormatState
}
bool need_comma = false;
if (all)
{
if (std::exchange(need_comma, true))
@ -38,11 +39,11 @@ void ASTRolesOrUsersSet::formatImpl(const FormatSettings & settings, FormatState
}
else
{
for (const auto & role : names)
for (const auto & name : names)
{
if (std::exchange(need_comma, true))
settings.ostr << ", ";
formatRoleNameOrID(role, id_mode, settings);
formatNameOrID(name, id_mode, settings);
}
if (current_user)
@ -58,11 +59,11 @@ void ASTRolesOrUsersSet::formatImpl(const FormatSettings & settings, FormatState
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " EXCEPT " << (settings.hilite ? IAST::hilite_none : "");
need_comma = false;
for (const auto & except_role : except_names)
for (const auto & name : except_names)
{
if (std::exchange(need_comma, true))
settings.ostr << ", ";
formatRoleNameOrID(except_role, id_mode, settings);
formatNameOrID(name, id_mode, settings);
}
if (except_current_user)
@ -75,7 +76,7 @@ void ASTRolesOrUsersSet::formatImpl(const FormatSettings & settings, FormatState
}
void ASTRolesOrUsersSet::replaceCurrentUserTagWithName(const String & current_user_name)
void ASTRolesOrUsersSet::replaceCurrentUserTag(const String & current_user_name)
{
if (current_user)
{

View File

@ -9,22 +9,23 @@ namespace DB
using Strings = std::vector<String>;
/// Represents a set of users/roles like
/// {user_name | role_name | CURRENT_USER} [,...] | NONE | ALL | ALL EXCEPT {user_name | role_name | CURRENT_USER} [,...]
/// {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]
/// [EXCEPT {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]]
class ASTRolesOrUsersSet : public IAST
{
public:
bool all = false;
Strings names;
bool current_user = false;
bool all = false;
Strings except_names;
bool except_current_user = false;
bool id_mode = false; /// true if `names` and `except_names` keep UUIDs, not names.
bool allow_role_names = true; /// true if this set can contain names of roles.
bool allow_user_names = true; /// true if this set can contain names of users.
bool allow_users = true; /// whether this set can contain names of users
bool allow_roles = true; /// whether this set can contain names of roles
bool id_mode = false; /// whether this set keep UUIDs instead of names
bool empty() const { return names.empty() && !current_user && !all; }
void replaceCurrentUserTagWithName(const String & current_user_name);
void replaceCurrentUserTag(const String & current_user_name);
String getID(char) const override { return "RolesOrUsersSet"; }
ASTPtr clone() const override { return std::make_shared<ASTRolesOrUsersSet>(*this); }

View File

@ -23,7 +23,7 @@ void ASTRowPolicyName::formatImpl(const FormatSettings & settings, FormatState &
}
void ASTRowPolicyName::replaceEmptyDatabaseWithCurrent(const String & current_database)
void ASTRowPolicyName::replaceEmptyDatabase(const String & current_database)
{
if (name_parts.database.empty())
name_parts.database = current_database;
@ -125,7 +125,7 @@ Strings ASTRowPolicyNames::toStrings() const
}
void ASTRowPolicyNames::replaceEmptyDatabaseWithCurrent(const String & current_database)
void ASTRowPolicyNames::replaceEmptyDatabase(const String & current_database)
{
for (auto & np : name_parts)
if (np.database.empty())

View File

@ -22,7 +22,7 @@ public:
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTRowPolicyName>(clone()); }
void replaceEmptyDatabaseWithCurrent(const String & current_database);
void replaceEmptyDatabase(const String & current_database);
};
@ -44,6 +44,6 @@ public:
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
ASTPtr getRewrittenASTWithoutOnCluster(const std::string &) const override { return removeOnCluster<ASTRowPolicyNames>(clone()); }
void replaceEmptyDatabaseWithCurrent(const String & current_database);
void replaceEmptyDatabase(const String & current_database);
};
}

View File

@ -43,7 +43,7 @@ void ASTShowAccessEntitiesQuery::formatQueryImpl(const FormatSettings & settings
}
void ASTShowAccessEntitiesQuery::replaceEmptyDatabaseWithCurrent(const String & current_database)
void ASTShowAccessEntitiesQuery::replaceEmptyDatabase(const String & current_database)
{
if (database_and_table_name)
{

View File

@ -31,7 +31,7 @@ public:
String getID(char) const override;
ASTPtr clone() const override { return std::make_shared<ASTShowAccessEntitiesQuery>(*this); }
void replaceEmptyDatabaseWithCurrent(const String & current_database);
void replaceEmptyDatabase(const String & current_database);
protected:
void formatQueryImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;

View File

@ -72,10 +72,10 @@ void ASTShowCreateAccessEntityQuery::formatQueryImpl(const FormatSettings & sett
}
void ASTShowCreateAccessEntityQuery::replaceEmptyDatabaseWithCurrent(const String & current_database)
void ASTShowCreateAccessEntityQuery::replaceEmptyDatabase(const String & current_database)
{
if (row_policy_names)
row_policy_names->replaceEmptyDatabaseWithCurrent(current_database);
row_policy_names->replaceEmptyDatabase(current_database);
if (database_and_table_name)
{

View File

@ -40,7 +40,7 @@ public:
String getID(char) const override;
ASTPtr clone() const override;
void replaceEmptyDatabaseWithCurrent(const String & current_database);
void replaceEmptyDatabase(const String & current_database);
protected:
String getKeyword() const;

View File

@ -226,7 +226,7 @@ namespace
{
ASTPtr node;
ParserRolesOrUsersSet roles_p;
roles_p.allowAll().allowRoleNames().allowUserNames().allowCurrentUser().useIDMode(id_mode);
roles_p.allowAll().allowRoles().allowUsers().allowCurrentUser().useIDMode(id_mode);
if (!ParserKeyword{"TO"}.ignore(pos, expected) || !roles_p.parse(pos, node, expected))
return false;

View File

@ -187,7 +187,7 @@ namespace
return false;
ParserRolesOrUsersSet roles_p;
roles_p.allowAll().allowRoleNames().allowUserNames().allowCurrentUser().useIDMode(id_mode);
roles_p.allowAll().allowRoles().allowUsers().allowCurrentUser().useIDMode(id_mode);
if (!roles_p.parse(pos, ast, expected))
return false;

View File

@ -53,7 +53,7 @@ namespace
return false;
ParserRolesOrUsersSet roles_p;
roles_p.allowAll().allowRoleNames().allowUserNames().allowCurrentUser().useIDMode(id_mode);
roles_p.allowAll().allowRoles().allowUsers().allowCurrentUser().useIDMode(id_mode);
if (!roles_p.parse(pos, ast, expected))
return false;

View File

@ -246,12 +246,12 @@ namespace
ASTPtr ast;
ParserRolesOrUsersSet default_roles_p;
default_roles_p.allowAll().allowRoleNames().useIDMode(id_mode);
default_roles_p.allowAll().allowRoles().useIDMode(id_mode);
if (!default_roles_p.parse(pos, ast, expected))
return false;
default_roles = typeid_cast<std::shared_ptr<ASTRolesOrUsersSet>>(ast);
default_roles->allow_user_names = false;
default_roles->allow_users = false;
return true;
});
}

View File

@ -9,12 +9,14 @@ namespace DB
* CREATE USER [IF NOT EXISTS | OR REPLACE] name
* [NOT IDENTIFIED | IDENTIFIED {[WITH {no_password|plaintext_password|sha256_password|sha256_hash|double_sha1_password|double_sha1_hash}] BY {'password'|'hash'}}|{WITH ldap SERVER 'server_name'}|{WITH kerberos [REALM 'realm']}]
* [HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [DEFAULT ROLE role [,...]]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*
* ALTER USER [IF EXISTS] name
* [RENAME TO new_name]
* [NOT IDENTIFIED | IDENTIFIED {[WITH {no_password|plaintext_password|sha256_password|sha256_hash|double_sha1_password|double_sha1_hash}] BY {'password'|'hash'}}|{WITH ldap SERVER 'server_name'}|{WITH kerberos [REALM 'realm']}]
* [[ADD|DROP] HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [DEFAULT ROLE role [,...] | ALL | ALL EXCEPT role [,...] ]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*/
class ParserCreateUserQuery : public IParserBase

View File

@ -8,6 +8,7 @@
#include <Parsers/ParserRolesOrUsersSet.h>
#include <Parsers/parseDatabaseAndTableName.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
namespace DB
@ -20,8 +21,6 @@ namespace ErrorCodes
namespace
{
using Kind = ASTGrantQuery::Kind;
bool parseAccessFlags(IParser::Pos & pos, Expected & expected, AccessFlags & access_flags)
{
static constexpr auto is_one_of_access_type_words = [](IParser::Pos & pos_)
@ -87,7 +86,7 @@ namespace
});
}
bool parseAccessTypesWithColumns(IParser::Pos & pos, Expected & expected,
bool parseAccessFlagsWithColumns(IParser::Pos & pos, Expected & expected,
std::vector<std::pair<AccessFlags, Strings>> & access_and_columns)
{
std::vector<std::pair<AccessFlags, Strings>> res;
@ -112,7 +111,7 @@ namespace
}
bool parseAccessRightsElements(IParser::Pos & pos, Expected & expected, AccessRightsElements & elements)
bool parseElementsWithoutOptions(IParser::Pos & pos, Expected & expected, AccessRightsElements & elements)
{
return IParserBase::wrapParseImpl(pos, [&]
{
@ -121,7 +120,7 @@ namespace
auto parse_around_on = [&]
{
std::vector<std::pair<AccessFlags, Strings>> access_and_columns;
if (!parseAccessTypesWithColumns(pos, expected, access_and_columns))
if (!parseAccessFlagsWithColumns(pos, expected, access_and_columns))
return false;
if (!ParserKeyword{"ON"}.ignore(pos, expected))
@ -157,16 +156,16 @@ namespace
}
void removeNonGrantableFlags(AccessRightsElements & elements)
void eraseNonGrantable(AccessRightsElements & elements)
{
for (auto & element : elements)
boost::range::remove_erase_if(elements, [](AccessRightsElement & element)
{
if (element.empty())
continue;
return true;
auto old_flags = element.access_flags;
element.removeNonGrantableFlags();
element.eraseNonGrantable();
if (!element.empty())
continue;
return false;
if (!element.any_column)
throw Exception(old_flags.toString() + " cannot be granted on the column level", ErrorCodes::INVALID_GRANT);
@ -176,17 +175,17 @@ namespace
throw Exception(old_flags.toString() + " cannot be granted on the database level", ErrorCodes::INVALID_GRANT);
else
throw Exception(old_flags.toString() + " cannot be granted", ErrorCodes::INVALID_GRANT);
}
});
}
bool parseRoles(IParser::Pos & pos, Expected & expected, Kind kind, bool id_mode, std::shared_ptr<ASTRolesOrUsersSet> & roles)
bool parseRoles(IParser::Pos & pos, Expected & expected, bool is_revoke, bool id_mode, std::shared_ptr<ASTRolesOrUsersSet> & roles)
{
return IParserBase::wrapParseImpl(pos, [&]
{
ParserRolesOrUsersSet roles_p;
roles_p.allowRoleNames().useIDMode(id_mode);
if (kind == Kind::REVOKE)
roles_p.allowRoles().useIDMode(id_mode);
if (is_revoke)
roles_p.allowAll();
ASTPtr ast;
@ -199,28 +198,20 @@ namespace
}
bool parseToRoles(IParser::Pos & pos, Expected & expected, ASTGrantQuery::Kind kind, std::shared_ptr<ASTRolesOrUsersSet> & to_roles)
bool parseToGrantees(IParser::Pos & pos, Expected & expected, bool is_revoke, std::shared_ptr<ASTRolesOrUsersSet> & grantees)
{
return IParserBase::wrapParseImpl(pos, [&]
{
if (kind == Kind::GRANT)
{
if (!ParserKeyword{"TO"}.ignore(pos, expected))
if (!ParserKeyword{is_revoke ? "FROM" : "TO"}.ignore(pos, expected))
return false;
}
else
{
if (!ParserKeyword{"FROM"}.ignore(pos, expected))
return false;
}
ASTPtr ast;
ParserRolesOrUsersSet roles_p;
roles_p.allowRoleNames().allowUserNames().allowCurrentUser().allowAll(kind == Kind::REVOKE);
roles_p.allowRoles().allowUsers().allowCurrentUser().allowAll(is_revoke);
if (!roles_p.parse(pos, ast, expected))
return false;
to_roles = typeid_cast<std::shared_ptr<ASTRolesOrUsersSet>>(ast);
grantees = typeid_cast<std::shared_ptr<ASTRolesOrUsersSet>>(ast);
return true;
});
}
@ -237,20 +228,13 @@ namespace
bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
bool attach = false;
if (attach_mode)
{
if (!ParserKeyword{"ATTACH"}.ignore(pos, expected))
if (attach_mode && !ParserKeyword{"ATTACH"}.ignore(pos, expected))
return false;
attach = true;
}
Kind kind;
if (ParserKeyword{"GRANT"}.ignore(pos, expected))
kind = Kind::GRANT;
else if (ParserKeyword{"REVOKE"}.ignore(pos, expected))
kind = Kind::REVOKE;
else
bool is_revoke = false;
if (ParserKeyword{"REVOKE"}.ignore(pos, expected))
is_revoke = true;
else if (!ParserKeyword{"GRANT"}.ignore(pos, expected))
return false;
String cluster;
@ -259,7 +243,7 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
bool grant_option = false;
bool admin_option = false;
if (kind == Kind::REVOKE)
if (is_revoke)
{
if (ParserKeyword{"GRANT OPTION FOR"}.ignore(pos, expected))
grant_option = true;
@ -269,20 +253,20 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
AccessRightsElements elements;
std::shared_ptr<ASTRolesOrUsersSet> roles;
if (!parseAccessRightsElements(pos, expected, elements) && !parseRoles(pos, expected, kind, attach, roles))
if (!parseElementsWithoutOptions(pos, expected, elements) && !parseRoles(pos, expected, is_revoke, attach_mode, roles))
return false;
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
std::shared_ptr<ASTRolesOrUsersSet> to_roles;
if (!parseToRoles(pos, expected, kind, to_roles))
std::shared_ptr<ASTRolesOrUsersSet> grantees;
if (!parseToGrantees(pos, expected, is_revoke, grantees))
return false;
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
if (kind == Kind::GRANT)
if (!is_revoke)
{
if (ParserKeyword{"WITH GRANT OPTION"}.ignore(pos, expected))
grant_option = true;
@ -298,19 +282,24 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
if (admin_option && !elements.empty())
throw Exception("ADMIN OPTION should be specified for roles", ErrorCodes::SYNTAX_ERROR);
if (kind == Kind::GRANT)
removeNonGrantableFlags(elements);
if (grant_option)
{
for (auto & element : elements)
element.grant_option = true;
}
if (!is_revoke)
eraseNonGrantable(elements);
auto query = std::make_shared<ASTGrantQuery>();
node = query;
query->kind = kind;
query->attach = attach;
query->is_revoke = is_revoke;
query->attach_mode = attach_mode;
query->cluster = std::move(cluster);
query->access_rights_elements = std::move(elements);
query->roles = std::move(roles);
query->to_roles = std::move(to_roles);
query->grant_option = grant_option;
query->grantees = std::move(grantees);
query->admin_option = admin_option;
return true;

View File

@ -12,11 +12,7 @@ namespace DB
{
namespace
{
bool parseRoleNameOrID(
IParserBase::Pos & pos,
Expected & expected,
bool id_mode,
String & res)
bool parseNameOrID(IParserBase::Pos & pos, Expected & expected, bool id_mode, String & res)
{
return IParserBase::wrapParseImpl(pos, [&]
{
@ -39,20 +35,20 @@ namespace
});
}
bool parseBeforeExcept(
IParserBase::Pos & pos,
Expected & expected,
bool id_mode,
bool allow_all,
bool allow_current_user,
Strings & names,
bool & all,
Strings & names,
bool & current_user)
{
bool res_all = false;
bool res_current_user = false;
Strings res_names;
bool res_current_user = false;
Strings res_with_roles_names;
auto parse_element = [&]
{
@ -72,7 +68,7 @@ namespace
}
String name;
if (parseRoleNameOrID(pos, expected, id_mode, name))
if (parseNameOrID(pos, expected, id_mode, name))
{
res_names.emplace_back(std::move(name));
return true;
@ -85,8 +81,8 @@ namespace
return false;
names = std::move(res_names);
all = res_all;
current_user = res_current_user;
all = res_all;
return true;
}
@ -98,13 +94,12 @@ namespace
Strings & except_names,
bool & except_current_user)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return IParserBase::wrapParseImpl(pos, [&] {
if (!ParserKeyword{"EXCEPT"}.ignore(pos, expected))
return false;
bool unused;
return parseBeforeExcept(pos, expected, id_mode, false, allow_current_user, except_names, unused, except_current_user);
return parseBeforeExcept(pos, expected, id_mode, false, allow_current_user, unused, except_names, except_current_user);
});
}
}
@ -112,13 +107,13 @@ namespace
bool ParserRolesOrUsersSet::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
bool all = false;
Strings names;
bool current_user = false;
bool all = false;
Strings except_names;
bool except_current_user = false;
if (!parseBeforeExcept(pos, expected, id_mode, allow_all, allow_current_user, names, all, current_user))
if (!parseBeforeExcept(pos, expected, id_mode, allow_all, allow_current_user, all, names, current_user))
return false;
parseExceptAndAfterExcept(pos, expected, id_mode, allow_current_user, except_names, except_current_user);
@ -132,9 +127,9 @@ bool ParserRolesOrUsersSet::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
result->all = all;
result->except_names = std::move(except_names);
result->except_current_user = except_current_user;
result->allow_users = allow_users;
result->allow_roles = allow_roles;
result->id_mode = id_mode;
result->allow_user_names = allow_user_names;
result->allow_role_names = allow_role_names;
node = result;
return true;
}

View File

@ -6,15 +6,16 @@
namespace DB
{
/** Parses a string like this:
* {role|CURRENT_USER} [,...] | NONE | ALL | ALL EXCEPT {role|CURRENT_USER} [,...]
* {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]
* [EXCEPT {user_name | role_name | CURRENT_USER | ALL | NONE} [,...]]
*/
class ParserRolesOrUsersSet : public IParserBase
{
public:
ParserRolesOrUsersSet & allowAll(bool allow_all_ = true) { allow_all = allow_all_; return *this; }
ParserRolesOrUsersSet & allowUserNames(bool allow_user_names_ = true) { allow_user_names = allow_user_names_; return *this; }
ParserRolesOrUsersSet & allowRoleNames(bool allow_role_names_ = true) { allow_role_names = allow_role_names_; return *this; }
ParserRolesOrUsersSet & allowUsers(bool allow_users_ = true) { allow_users = allow_users_; return *this; }
ParserRolesOrUsersSet & allowCurrentUser(bool allow_current_user_ = true) { allow_current_user = allow_current_user_; return *this; }
ParserRolesOrUsersSet & allowRoles(bool allow_roles_ = true) { allow_roles = allow_roles_; return *this; }
ParserRolesOrUsersSet & useIDMode(bool id_mode_ = true) { id_mode = id_mode_; return *this; }
protected:
@ -23,9 +24,9 @@ protected:
private:
bool allow_all = false;
bool allow_user_names = false;
bool allow_role_names = false;
bool allow_users = false;
bool allow_current_user = false;
bool allow_roles = false;
bool id_mode = false;
};

View File

@ -15,12 +15,12 @@ namespace
{
ASTPtr ast;
ParserRolesOrUsersSet roles_p;
roles_p.allowRoleNames().allowAll();
roles_p.allowRoles().allowAll();
if (!roles_p.parse(pos, ast, expected))
return false;
roles = typeid_cast<std::shared_ptr<ASTRolesOrUsersSet>>(ast);
roles->allow_user_names = false;
roles->allow_users = false;
return true;
});
}
@ -34,12 +34,12 @@ namespace
ASTPtr ast;
ParserRolesOrUsersSet users_p;
users_p.allowUserNames().allowCurrentUser();
users_p.allowUsers().allowCurrentUser();
if (!users_p.parse(pos, ast, expected))
return false;
to_users = typeid_cast<std::shared_ptr<ASTRolesOrUsersSet>>(ast);
to_users->allow_role_names = false;
to_users->allow_roles = false;
return true;
});
}

View File

@ -19,7 +19,7 @@ bool ParserShowGrantsQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
{
ASTPtr for_roles_ast;
ParserRolesOrUsersSet for_roles_p;
for_roles_p.allowUserNames().allowRoleNames().allowAll().allowCurrentUser();
for_roles_p.allowUsers().allowRoles().allowAll().allowCurrentUser();
if (!for_roles_p.parse(pos, for_roles_ast, expected))
return false;

View File

@ -18,7 +18,6 @@
namespace DB
{
using EntityType = IAccessEntity::Type;
using Kind = AccessRightsElementWithOptions::Kind;
NamesAndTypesList StorageSystemGrants::getNamesAndTypes()
{
@ -64,7 +63,7 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
const String * database,
const String * table,
const String * column,
Kind kind,
bool is_partial_revoke,
bool grant_option)
{
if (grantee_type == EntityType::USER)
@ -119,13 +118,13 @@ void StorageSystemGrants::fillData(MutableColumns & res_columns, const Context &
column_column_null_map.push_back(true);
}
column_is_partial_revoke.push_back(kind == Kind::REVOKE);
column_is_partial_revoke.push_back(is_partial_revoke);
column_grant_option.push_back(grant_option);
};
auto add_rows = [&](const String & grantee_name,
IAccessEntity::Type grantee_type,
const AccessRightsElementsWithOptions & elements)
const AccessRightsElements & elements)
{
for (const auto & element : elements)
{
@ -139,13 +138,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, element.kind, element.grant_option);
add_row(grantee_name, grantee_type, access_type, database, table, nullptr, element.is_partial_revoke, 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, element.kind, element.grant_option);
add_row(grantee_name, grantee_type, access_type, database, table, &column, element.is_partial_revoke, element.grant_option);
}
}
};

View File

@ -80,15 +80,17 @@ void StorageSystemRoleGrants::fillData(MutableColumns & res_columns, const Conte
const GrantedRoles & granted_roles,
const RolesOrUsersSet * default_roles)
{
for (const auto & role_id : granted_roles.roles)
for (const auto & element : granted_roles.getElements())
{
for (const auto & role_id : element.ids)
{
auto role_name = access_control.tryReadName(role_id);
if (!role_name)
continue;
bool is_default = !default_roles || default_roles->match(role_id);
bool with_admin_option = granted_roles.roles_with_admin_option.count(role_id);
add_row(grantee_name, grantee_type, *role_name, is_default, with_admin_option);
add_row(grantee_name, grantee_type, *role_name, is_default, element.admin_option);
}
}
};

View File

@ -166,6 +166,7 @@ def feature(self, node="clickhouse1"):
with Scenario("I revoke a role on fake cluster, throws exception", requirements=[
RQ_SRS_006_RBAC_Revoke_Role_Cluster("1.0")]):
with setup():
with When("I revoke a role from user on a cluster"):
exitcode, message = errors.cluster_not_found("fake_cluster")
node.query("REVOKE ON CLUSTER fake_cluster role0 FROM user0", exitcode=exitcode, message=message)