mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-12 09:22:05 +00:00
455 lines
19 KiB
C++
455 lines
19 KiB
C++
#include <Interpreters/InterpreterGrantQuery.h>
|
|
#include <Interpreters/QueryLog.h>
|
|
#include <Parsers/ASTGrantQuery.h>
|
|
#include <Parsers/ASTRolesOrUsersSet.h>
|
|
#include <Interpreters/Context.h>
|
|
#include <Interpreters/executeDDLQueryOnCluster.h>
|
|
#include <Access/AccessControlManager.h>
|
|
#include <Access/ContextAccess.h>
|
|
#include <Access/RolesOrUsersSet.h>
|
|
#include <Access/User.h>
|
|
#include <Access/Role.h>
|
|
#include <boost/range/algorithm/copy.hpp>
|
|
#include <boost/range/algorithm/set_algorithm.hpp>
|
|
|
|
namespace DB
|
|
{
|
|
namespace ErrorCodes
|
|
{
|
|
extern const int ACCESS_DENIED;
|
|
extern const int LOGICAL_ERROR;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/// Extracts access rights elements which are going to be granted or revoked from a query.
|
|
void collectAccessRightsElementsToGrantOrRevoke(
|
|
const ASTGrantQuery & query,
|
|
AccessRightsElements & elements_to_grant,
|
|
AccessRightsElements & elements_to_revoke)
|
|
{
|
|
elements_to_grant.clear();
|
|
elements_to_revoke.clear();
|
|
|
|
if (query.is_revoke)
|
|
{
|
|
/// REVOKE
|
|
elements_to_revoke = query.access_rights_elements;
|
|
}
|
|
else if (query.replace_access)
|
|
{
|
|
/// GRANT WITH REPLACE OPTION
|
|
elements_to_grant = query.access_rights_elements;
|
|
elements_to_revoke.emplace_back(AccessType::ALL);
|
|
}
|
|
else
|
|
{
|
|
/// GRANT
|
|
elements_to_grant = query.access_rights_elements;
|
|
}
|
|
}
|
|
|
|
/// Extracts roles which are going to be granted or revoked from a query.
|
|
void collectRolesToGrantOrRevoke(
|
|
const AccessControlManager & access_control,
|
|
const ASTGrantQuery & query,
|
|
std::vector<UUID> & roles_to_grant,
|
|
RolesOrUsersSet & roles_to_revoke)
|
|
{
|
|
roles_to_grant.clear();
|
|
roles_to_revoke.clear();
|
|
|
|
RolesOrUsersSet roles_to_grant_or_revoke;
|
|
if (query.roles)
|
|
roles_to_grant_or_revoke = RolesOrUsersSet{*query.roles, access_control};
|
|
|
|
if (query.is_revoke)
|
|
{
|
|
/// REVOKE
|
|
roles_to_revoke = std::move(roles_to_grant_or_revoke);
|
|
}
|
|
else if (query.replace_granted_roles)
|
|
{
|
|
/// GRANT WITH REPLACE OPTION
|
|
roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs(access_control);
|
|
roles_to_revoke = RolesOrUsersSet::AllTag{};
|
|
}
|
|
else
|
|
{
|
|
/// GRANT
|
|
roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs(access_control);
|
|
}
|
|
}
|
|
|
|
/// Extracts roles which are going to be granted or revoked from a query.
|
|
void collectRolesToGrantOrRevoke(
|
|
const ASTGrantQuery & query,
|
|
std::vector<UUID> & roles_to_grant,
|
|
RolesOrUsersSet & roles_to_revoke)
|
|
{
|
|
roles_to_grant.clear();
|
|
roles_to_revoke.clear();
|
|
|
|
RolesOrUsersSet roles_to_grant_or_revoke;
|
|
if (query.roles)
|
|
roles_to_grant_or_revoke = RolesOrUsersSet{*query.roles};
|
|
|
|
if (query.is_revoke)
|
|
{
|
|
/// REVOKE
|
|
roles_to_revoke = std::move(roles_to_grant_or_revoke);
|
|
}
|
|
else if (query.replace_granted_roles)
|
|
{
|
|
/// GRANT WITH REPLACE OPTION
|
|
roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs();
|
|
roles_to_revoke = RolesOrUsersSet::AllTag{};
|
|
}
|
|
else
|
|
{
|
|
/// GRANT
|
|
roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs();
|
|
}
|
|
}
|
|
|
|
/// Checks if a grantee is allowed for the current user, throws an exception if not.
|
|
void checkGranteeIsAllowed(const ContextAccess & current_user_access, const UUID & grantee_id, const IAccessEntity & grantee)
|
|
{
|
|
auto current_user = current_user_access.getUser();
|
|
if (current_user && !current_user->grantees.match(grantee_id))
|
|
throw Exception(grantee.outputTypeAndName() + " is not allowed as grantee", ErrorCodes::ACCESS_DENIED);
|
|
}
|
|
|
|
/// Checks if grantees are allowed for the current user, throws an exception if not.
|
|
void checkGranteesAreAllowed(const AccessControlManager & access_control, const ContextAccess & current_user_access, const std::vector<UUID> & grantee_ids)
|
|
{
|
|
auto current_user = current_user_access.getUser();
|
|
if (!current_user || (current_user->grantees == RolesOrUsersSet::AllTag{}))
|
|
return;
|
|
|
|
for (const auto & id : grantee_ids)
|
|
{
|
|
auto entity = access_control.tryRead(id);
|
|
if (auto role = typeid_cast<RolePtr>(entity))
|
|
checkGranteeIsAllowed(current_user_access, id, *role);
|
|
else if (auto user = typeid_cast<UserPtr>(entity))
|
|
checkGranteeIsAllowed(current_user_access, id, *user);
|
|
}
|
|
}
|
|
|
|
/// Checks if the current user has enough access rights granted with grant option to grant or revoke specified access rights.
|
|
void checkGrantOption(
|
|
const AccessControlManager & access_control,
|
|
const ContextAccess & current_user_access,
|
|
const std::vector<UUID> & grantees_from_query,
|
|
bool & need_check_grantees_are_allowed,
|
|
const AccessRightsElements & elements_to_grant,
|
|
AccessRightsElements & elements_to_revoke)
|
|
{
|
|
/// Check access rights which are going to be granted.
|
|
/// To execute the command GRANT the current user needs to have the access granted with GRANT OPTION.
|
|
current_user_access.checkGrantOption(elements_to_grant);
|
|
|
|
if (current_user_access.hasGrantOption(elements_to_revoke))
|
|
{
|
|
/// Simple case: the current user has the grant option for all the access rights specified for REVOKE.
|
|
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))
|
|
{
|
|
if (need_check_grantees_are_allowed)
|
|
checkGranteeIsAllowed(current_user_access, id, *role);
|
|
all_granted_access.makeUnion(role->access);
|
|
}
|
|
else if (auto user = typeid_cast<UserPtr>(entity))
|
|
{
|
|
if (need_check_grantees_are_allowed)
|
|
checkGranteeIsAllowed(current_user_access, id, *user);
|
|
all_granted_access.makeUnion(user->access);
|
|
}
|
|
}
|
|
|
|
need_check_grantees_are_allowed = false; /// already checked
|
|
|
|
if (!elements_to_revoke.empty() && elements_to_revoke[0].is_partial_revoke)
|
|
std::for_each(elements_to_revoke.begin(), elements_to_revoke.end(), [&](AccessRightsElement & element) { element.is_partial_revoke = false; });
|
|
AccessRights access_to_revoke;
|
|
access_to_revoke.grant(elements_to_revoke);
|
|
access_to_revoke.makeIntersection(all_granted_access);
|
|
|
|
/// Build more accurate list of elements to revoke, now we use an intesection of the initial list of elements to revoke
|
|
/// and all the granted access rights to these grantees.
|
|
bool grant_option = !elements_to_revoke.empty() && elements_to_revoke[0].grant_option;
|
|
elements_to_revoke.clear();
|
|
for (auto & element_to_revoke : access_to_revoke.getElements())
|
|
{
|
|
if (!element_to_revoke.is_partial_revoke && (element_to_revoke.grant_option || !grant_option))
|
|
elements_to_revoke.emplace_back(std::move(element_to_revoke));
|
|
}
|
|
|
|
current_user_access.checkGrantOption(elements_to_revoke);
|
|
}
|
|
|
|
/// Checks if the current user has enough roles granted with admin option to grant or revoke specified roles.
|
|
void checkAdminOption(
|
|
const AccessControlManager & access_control,
|
|
const ContextAccess & current_user_access,
|
|
const std::vector<UUID> & grantees_from_query,
|
|
bool & need_check_grantees_are_allowed,
|
|
const std::vector<UUID> & roles_to_grant,
|
|
RolesOrUsersSet & roles_to_revoke,
|
|
bool admin_option)
|
|
{
|
|
/// Check roles which are going to be granted.
|
|
/// To execute the command GRANT the current user needs to have the roles granted with ADMIN OPTION.
|
|
current_user_access.checkAdminOption(roles_to_grant);
|
|
|
|
/// Check roles which are going to be revoked.
|
|
std::vector<UUID> roles_to_revoke_ids;
|
|
if (!roles_to_revoke.all)
|
|
{
|
|
roles_to_revoke_ids = roles_to_revoke.getMatchingIDs();
|
|
if (current_user_access.hasAdminOption(roles_to_revoke_ids))
|
|
{
|
|
/// Simple case: the current user has the admin option for all the roles specified for REVOKE.
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// 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))
|
|
{
|
|
if (need_check_grantees_are_allowed)
|
|
checkGranteeIsAllowed(current_user_access, id, *role);
|
|
all_granted_roles.makeUnion(role->granted_roles);
|
|
}
|
|
else if (auto user = typeid_cast<UserPtr>(entity))
|
|
{
|
|
if (need_check_grantees_are_allowed)
|
|
checkGranteeIsAllowed(current_user_access, id, *user);
|
|
all_granted_roles.makeUnion(user->granted_roles);
|
|
}
|
|
}
|
|
|
|
need_check_grantees_are_allowed = false; /// already checked
|
|
|
|
const auto & all_granted_roles_set = admin_option ? all_granted_roles.getGrantedWithAdminOption() : all_granted_roles.getGranted();
|
|
if (roles_to_revoke.all)
|
|
boost::range::set_difference(all_granted_roles_set, roles_to_revoke.except_ids, std::back_inserter(roles_to_revoke_ids));
|
|
else
|
|
boost::range::remove_erase_if(roles_to_revoke_ids, [&](const UUID & id) { return !all_granted_roles_set.count(id); });
|
|
|
|
roles_to_revoke = roles_to_revoke_ids;
|
|
current_user_access.checkAdminOption(roles_to_revoke_ids);
|
|
}
|
|
|
|
/// Returns access rights which should be checked for executing GRANT/REVOKE on cluster.
|
|
/// This function is less accurate than checkGrantOption() because it cannot use any information about
|
|
/// access rights the grantees currently have (due to those grantees are located on multiple nodes,
|
|
/// we just don't have the full information about them).
|
|
AccessRightsElements getRequiredAccessForExecutingOnCluster(const AccessRightsElements & elements_to_grant, const AccessRightsElements & elements_to_revoke)
|
|
{
|
|
auto required_access = elements_to_grant;
|
|
required_access.insert(required_access.end(), elements_to_revoke.begin(), elements_to_revoke.end());
|
|
std::for_each(required_access.begin(), required_access.end(), [&](AccessRightsElement & element) { element.grant_option = true; });
|
|
return required_access;
|
|
}
|
|
|
|
/// Checks if the current user has enough roles granted with admin option to grant or revoke specified roles on cluster.
|
|
/// This function is less accurate than checkAdminOption() because it cannot use any information about
|
|
/// granted roles the grantees currently have (due to those grantees are located on multiple nodes,
|
|
/// we just don't have the full information about them).
|
|
void checkAdminOptionForExecutingOnCluster(const ContextAccess & current_user_access,
|
|
const std::vector<UUID> roles_to_grant,
|
|
const RolesOrUsersSet & roles_to_revoke)
|
|
{
|
|
if (roles_to_revoke.all)
|
|
{
|
|
/// Revoking all the roles on cluster always requires ROLE_ADMIN privilege
|
|
/// because when we send the query REVOKE ALL to each shard we don't know at this point
|
|
/// which roles exactly this is going to revoke on each shard.
|
|
/// However ROLE_ADMIN just allows to revoke every role, that's why we check it here.
|
|
current_user_access.checkAccess(AccessType::ROLE_ADMIN);
|
|
return;
|
|
}
|
|
|
|
if (current_user_access.isGranted(AccessType::ROLE_ADMIN))
|
|
return;
|
|
|
|
for (const auto & role_id : roles_to_grant)
|
|
current_user_access.checkAdminOption(role_id);
|
|
|
|
|
|
for (const auto & role_id : roles_to_revoke.getMatchingIDs())
|
|
current_user_access.checkAdminOption(role_id);
|
|
}
|
|
|
|
template <typename T>
|
|
void updateGrantedAccessRightsAndRolesTemplate(
|
|
T & grantee,
|
|
const AccessRightsElements & elements_to_grant,
|
|
const AccessRightsElements & elements_to_revoke,
|
|
const std::vector<UUID> & roles_to_grant,
|
|
const RolesOrUsersSet & roles_to_revoke,
|
|
bool admin_option)
|
|
{
|
|
if (!elements_to_revoke.empty())
|
|
grantee.access.revoke(elements_to_revoke);
|
|
|
|
if (!elements_to_grant.empty())
|
|
grantee.access.grant(elements_to_grant);
|
|
|
|
if (!roles_to_revoke.empty())
|
|
{
|
|
if (admin_option)
|
|
grantee.granted_roles.revokeAdminOption(grantee.granted_roles.findGrantedWithAdminOption(roles_to_revoke));
|
|
else
|
|
grantee.granted_roles.revoke(grantee.granted_roles.findGranted(roles_to_revoke));
|
|
}
|
|
|
|
if (!roles_to_grant.empty())
|
|
{
|
|
if (admin_option)
|
|
grantee.granted_roles.grantWithAdminOption(roles_to_grant);
|
|
else
|
|
grantee.granted_roles.grant(roles_to_grant);
|
|
}
|
|
}
|
|
|
|
/// Updates grants of a specified user or role.
|
|
void updateGrantedAccessRightsAndRoles(
|
|
IAccessEntity & grantee,
|
|
const AccessRightsElements & elements_to_grant,
|
|
const AccessRightsElements & elements_to_revoke,
|
|
const std::vector<UUID> & roles_to_grant,
|
|
const RolesOrUsersSet & roles_to_revoke,
|
|
bool admin_option)
|
|
{
|
|
if (auto * user = typeid_cast<User *>(&grantee))
|
|
updateGrantedAccessRightsAndRolesTemplate(*user, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, admin_option);
|
|
else if (auto * role = typeid_cast<Role *>(&grantee))
|
|
updateGrantedAccessRightsAndRolesTemplate(*role, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, admin_option);
|
|
}
|
|
|
|
/// Updates grants of a specified user or role.
|
|
void updateFromQuery(IAccessEntity & grantee, const ASTGrantQuery & query)
|
|
{
|
|
AccessRightsElements elements_to_grant, elements_to_revoke;
|
|
collectAccessRightsElementsToGrantOrRevoke(query, elements_to_grant, elements_to_revoke);
|
|
|
|
std::vector<UUID> roles_to_grant;
|
|
RolesOrUsersSet roles_to_revoke;
|
|
collectRolesToGrantOrRevoke(query, roles_to_grant, roles_to_revoke);
|
|
|
|
updateGrantedAccessRightsAndRoles(grantee, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, query.admin_option);
|
|
}
|
|
}
|
|
|
|
|
|
BlockIO InterpreterGrantQuery::execute()
|
|
{
|
|
auto & query = query_ptr->as<ASTGrantQuery &>();
|
|
|
|
query.replaceCurrentUserTag(getContext()->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_control = getContext()->getAccessControlManager();
|
|
auto current_user_access = getContext()->getAccess();
|
|
|
|
std::vector<UUID> grantees = RolesOrUsersSet{*query.grantees, access_control, getContext()->getUserID()}.getMatchingIDs(access_control);
|
|
|
|
/// Collect access rights and roles we're going to grant or revoke.
|
|
AccessRightsElements elements_to_grant, elements_to_revoke;
|
|
collectAccessRightsElementsToGrantOrRevoke(query, elements_to_grant, elements_to_revoke);
|
|
|
|
std::vector<UUID> roles_to_grant;
|
|
RolesOrUsersSet roles_to_revoke;
|
|
collectRolesToGrantOrRevoke(access_control, query, roles_to_grant, roles_to_revoke);
|
|
|
|
/// Executing on cluster.
|
|
if (!query.cluster.empty())
|
|
{
|
|
auto required_access = getRequiredAccessForExecutingOnCluster(elements_to_grant, elements_to_revoke);
|
|
checkAdminOptionForExecutingOnCluster(*current_user_access, roles_to_grant, roles_to_revoke);
|
|
checkGranteesAreAllowed(access_control, *current_user_access, grantees);
|
|
return executeDDLQueryOnCluster(query_ptr, getContext(), std::move(required_access));
|
|
}
|
|
|
|
/// Check if the current user has corresponding access rights granted with grant option.
|
|
String current_database = getContext()->getCurrentDatabase();
|
|
elements_to_grant.replaceEmptyDatabase(current_database);
|
|
elements_to_revoke.replaceEmptyDatabase(current_database);
|
|
bool need_check_grantees_are_allowed = true;
|
|
checkGrantOption(access_control, *current_user_access, grantees, need_check_grantees_are_allowed, elements_to_grant, elements_to_revoke);
|
|
|
|
/// Check if the current user has corresponding roles granted with admin option.
|
|
checkAdminOption(access_control, *current_user_access, grantees, need_check_grantees_are_allowed, roles_to_grant, roles_to_revoke, query.admin_option);
|
|
|
|
if (need_check_grantees_are_allowed)
|
|
checkGranteesAreAllowed(access_control, *current_user_access, grantees);
|
|
|
|
/// Update roles and users listed in `grantees`.
|
|
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
|
|
{
|
|
auto clone = entity->clone();
|
|
updateGrantedAccessRightsAndRoles(*clone, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, query.admin_option);
|
|
return clone;
|
|
};
|
|
|
|
access_control.update(grantees, update_func);
|
|
|
|
return {};
|
|
}
|
|
|
|
|
|
void InterpreterGrantQuery::updateUserFromQuery(User & user, const ASTGrantQuery & query)
|
|
{
|
|
updateFromQuery(user, query);
|
|
}
|
|
|
|
void InterpreterGrantQuery::updateRoleFromQuery(Role & role, const ASTGrantQuery & query)
|
|
{
|
|
updateFromQuery(role, query);
|
|
}
|
|
|
|
|
|
void InterpreterGrantQuery::extendQueryLogElemImpl(QueryLogElement & elem, const ASTPtr & /*ast*/, ContextPtr) const
|
|
{
|
|
auto & query = query_ptr->as<ASTGrantQuery &>();
|
|
if (query.is_revoke)
|
|
elem.query_kind = "Revoke";
|
|
else
|
|
elem.query_kind = "Grant";
|
|
}
|
|
|
|
}
|