#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int ACCESS_DENIED; extern const int READONLY; extern const int QUERY_IS_PROHIBITED; extern const int FUNCTION_NOT_ALLOWED; extern const int UNKNOWN_USER; extern const int LOGICAL_ERROR; } namespace { AccessRights mixAccessRightsFromUserAndRoles(const User & user, const EnabledRolesInfo & roles_info) { AccessRights res = user.access; res.makeUnion(roles_info.access); return res; } AccessRights addImplicitAccessRights(const AccessRights & access, const AccessControl & access_control) { AccessFlags max_flags; auto modifier = [&](const AccessFlags & flags, const AccessFlags & min_flags_with_children, const AccessFlags & max_flags_with_children, std::string_view database, std::string_view table, std::string_view column, bool /* grant_option */) -> AccessFlags { size_t level = !database.empty() + !table.empty() + !column.empty(); AccessFlags res = flags; /// CREATE_TABLE => CREATE_VIEW, DROP_TABLE => DROP_VIEW, ALTER_TABLE => ALTER_VIEW static const AccessFlags create_table = AccessType::CREATE_TABLE; static const AccessFlags create_view = AccessType::CREATE_VIEW; static const AccessFlags drop_table = AccessType::DROP_TABLE; static const AccessFlags drop_view = AccessType::DROP_VIEW; static const AccessFlags alter_table = AccessType::ALTER_TABLE; static const AccessFlags alter_view = AccessType::ALTER_VIEW; if (res & create_table) res |= create_view; if (res & drop_table) res |= drop_view; if (res & alter_table) res |= alter_view; /// CREATE TABLE (on any database/table) => CREATE_TEMPORARY_TABLE (global) static const AccessFlags create_temporary_table = AccessType::CREATE_TEMPORARY_TABLE; if ((level == 0) && (max_flags_with_children & create_table)) res |= create_temporary_table; /// CREATE TABLE (on any database/table) => CREATE_ARBITRARY_TEMPORARY_TABLE (global) static const AccessFlags create_arbitrary_temporary_table = AccessType::CREATE_ARBITRARY_TEMPORARY_TABLE; if ((level == 0) && (max_flags_with_children & create_table)) res |= create_arbitrary_temporary_table; /// ALTER_TTL => ALTER_MATERIALIZE_TTL static const AccessFlags alter_ttl = AccessType::ALTER_TTL; static const AccessFlags alter_materialize_ttl = AccessType::ALTER_MATERIALIZE_TTL; if (res & alter_ttl) res |= alter_materialize_ttl; /// RELOAD_DICTIONARY (global) => RELOAD_EMBEDDED_DICTIONARIES (global) static const AccessFlags reload_dictionary = AccessType::SYSTEM_RELOAD_DICTIONARY; static const AccessFlags reload_embedded_dictionaries = AccessType::SYSTEM_RELOAD_EMBEDDED_DICTIONARIES; if ((level == 0) && (min_flags_with_children & reload_dictionary)) res |= reload_embedded_dictionaries; /// any column flag => SHOW_COLUMNS => SHOW_TABLES => SHOW_DATABASES /// any table flag => SHOW_TABLES => SHOW_DATABASES /// any dictionary flag => SHOW_DICTIONARIES => SHOW_DATABASES /// any database flag => SHOW_DATABASES static const AccessFlags show_columns = AccessType::SHOW_COLUMNS; static const AccessFlags show_tables = AccessType::SHOW_TABLES; static const AccessFlags show_dictionaries = AccessType::SHOW_DICTIONARIES; static const AccessFlags show_tables_or_dictionaries = show_tables | show_dictionaries; static const AccessFlags show_databases = AccessType::SHOW_DATABASES; if (res & AccessFlags::allColumnFlags()) res |= show_columns; if ((res & AccessFlags::allTableFlags()) || (level <= 2 && (res & show_columns)) || (level == 2 && (max_flags_with_children & show_columns))) { res |= show_tables; } if (res & AccessFlags::allDictionaryFlags()) res |= show_dictionaries; if ((res & AccessFlags::allDatabaseFlags()) || (level <= 1 && (res & show_tables_or_dictionaries)) || (level == 1 && (max_flags_with_children & show_tables_or_dictionaries))) { res |= show_databases; } max_flags |= res; return res; }; AccessRights res = access; res.modifyFlags(modifier); /// If "select_from_system_db_requires_grant" is enabled we provide implicit grants only for a few tables in the system database. if (access_control.doesSelectFromSystemDatabaseRequireGrant()) { const char * always_accessible_tables[] = { /// Constant tables "one", /// "numbers", "numbers_mt", "zeros", "zeros_mt" were excluded because they can generate lots of values and /// that can decrease performance in some cases. "contributors", "licenses", "time_zones", "collations", "formats", "privileges", "data_type_families", "database_engines", "table_engines", "table_functions", "aggregate_function_combinators", "functions", /// Can contain user-defined functions /// The following tables hide some rows if the current user doesn't have corresponding SHOW privileges. "databases", "tables", "columns", /// Specific to the current session "settings", "current_roles", "enabled_roles", "quota_usage" }; for (const auto * table_name : always_accessible_tables) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, table_name); if (max_flags.contains(AccessType::SHOW_USERS)) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "users"); if (max_flags.contains(AccessType::SHOW_ROLES)) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "roles"); if (max_flags.contains(AccessType::SHOW_ROW_POLICIES)) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "row_policies"); if (max_flags.contains(AccessType::SHOW_SETTINGS_PROFILES)) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "settings_profiles"); if (max_flags.contains(AccessType::SHOW_QUOTAS)) res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE, "quotas"); } else { res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE); } /// If "select_from_information_schema_requires_grant" is enabled we don't provide implicit grants for the information_schema database. if (!access_control.doesSelectFromInformationSchemaRequireGrant()) { res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA); res.grant(AccessType::SELECT, DatabaseCatalog::INFORMATION_SCHEMA_UPPERCASE); } return res; } std::array to_array(const UUID & id) { std::array ids; ids[0] = id; return ids; } /// Helper for using in templates. std::string_view getDatabase() { return {}; } template std::string_view getDatabase(std::string_view arg1, const OtherArgs &...) { return arg1; } } std::shared_ptr ContextAccess::fromContext(const ContextPtr & context) { return context->getAccess(); } ContextAccess::ContextAccess(const AccessControl & access_control_, const Params & params_) : access_control(&access_control_) , params(params_) { } ContextAccess::~ContextAccess() = default; void ContextAccess::initialize() { std::lock_guard lock{mutex}; if (params.full_access) { access = std::make_shared(AccessRights::getFullAccess()); access_with_implicit = access; return; } if (!params.user_id) throw Exception(ErrorCodes::LOGICAL_ERROR, "No user in current context, it's a bug"); subscription_for_user_change = access_control->subscribeForChanges( *params.user_id, [weak_ptr = weak_from_this()](const UUID &, const AccessEntityPtr & entity) { auto ptr = weak_ptr.lock(); if (!ptr) return; UserPtr changed_user = entity ? typeid_cast(entity) : nullptr; std::lock_guard lock2{ptr->mutex}; ptr->setUser(changed_user); if (!ptr->user && !ptr->user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041, a)"); }); setUser(access_control->read(*params.user_id)); if (!user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041, b)"); initialized = true; } void ContextAccess::setUser(const UserPtr & user_) const { user = user_; if (!user_) { /// User has been dropped. user_was_dropped = true; subscription_for_user_change = {}; subscription_for_roles_changes = {}; access = nullptr; access_with_implicit = nullptr; enabled_roles = nullptr; roles_info = nullptr; enabled_row_policies = nullptr; row_policies_of_initial_user = nullptr; enabled_quota = nullptr; enabled_settings = nullptr; return; } user_name = user->getName(); trace_log = getLogger("ContextAccess (" + user_name + ")"); std::vector current_roles, current_roles_with_admin_option; if (params.use_default_roles) { current_roles = user->granted_roles.findGranted(user->default_roles); current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(user->default_roles); } else if (params.current_roles) { current_roles = user->granted_roles.findGranted(*params.current_roles); current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(*params.current_roles); } subscription_for_roles_changes.reset(); enabled_roles = access_control->getEnabledRoles(current_roles, current_roles_with_admin_option); subscription_for_roles_changes = enabled_roles->subscribeForChanges([this](const std::shared_ptr & roles_info_) { std::lock_guard lock{mutex}; setRolesInfo(roles_info_); }); setRolesInfo(enabled_roles->getRolesInfo()); std::optional initial_user_id; if (!params.initial_user.empty()) initial_user_id = access_control->find(params.initial_user); row_policies_of_initial_user = initial_user_id ? access_control->tryGetDefaultRowPolicies(*initial_user_id) : nullptr; } void ContextAccess::setRolesInfo(const std::shared_ptr & roles_info_) const { assert(roles_info_); roles_info = roles_info_; enabled_row_policies = access_control->getEnabledRowPolicies(*params.user_id, roles_info->enabled_roles); enabled_settings = access_control->getEnabledSettings( *params.user_id, user->settings, roles_info->enabled_roles, roles_info->settings_from_enabled_roles); calculateAccessRights(); } void ContextAccess::calculateAccessRights() const { access = std::make_shared(mixAccessRightsFromUserAndRoles(*user, *roles_info)); access_with_implicit = std::make_shared(addImplicitAccessRights(*access, *access_control)); if (trace_log) { if (roles_info && !roles_info->getCurrentRolesNames().empty()) { LOG_TRACE(trace_log, "Current_roles: {}, enabled_roles: {}", boost::algorithm::join(roles_info->getCurrentRolesNames(), ", "), boost::algorithm::join(roles_info->getEnabledRolesNames(), ", ")); } LOG_TRACE(trace_log, "Settings: readonly = {}, allow_ddl = {}, allow_introspection_functions = {}", params.readonly, params.allow_ddl, params.allow_introspection); LOG_TRACE(trace_log, "List of all grants: {}", access->toString()); LOG_TRACE(trace_log, "List of all grants including implicit: {}", access_with_implicit->toString()); } } UserPtr ContextAccess::getUser() const { auto res = tryGetUser(); if (likely(res)) return res; if (user_was_dropped) throw Exception(ErrorCodes::UNKNOWN_USER, "User has been dropped"); throw Exception(ErrorCodes::LOGICAL_ERROR, "No user in current context, it's a bug"); } UserPtr ContextAccess::tryGetUser() const { std::lock_guard lock{mutex}; return user; } String ContextAccess::getUserName() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); return user_name; } std::shared_ptr ContextAccess::getRolesInfo() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (roles_info) return roles_info; static const auto no_roles = std::make_shared(); return no_roles; } RowPolicyFilterPtr ContextAccess::getRowPolicyFilter(const String & database, const String & table_name, RowPolicyFilterType filter_type) const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); RowPolicyFilterPtr filter; if (enabled_row_policies) filter = enabled_row_policies->getFilter(database, table_name, filter_type); if (row_policies_of_initial_user) { /// Find and set extra row policies to be used based on `client_info.initial_user`, if the initial user exists. /// TODO: we need a better solution here. It seems we should pass the initial row policy /// because a shard is allowed to not have the initial user or it might be another user /// with the same name. filter = row_policies_of_initial_user->getFilter(database, table_name, filter_type, filter); } return filter; } std::shared_ptr ContextAccess::getQuota() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (!enabled_quota) { if (roles_info) { enabled_quota = access_control->getEnabledQuota(*params.user_id, user_name, roles_info->enabled_roles, params.address, params.forwarded_address, params.quota_key); } else { static const auto unlimited_quota = EnabledQuota::getUnlimitedQuota(); return unlimited_quota; } } return enabled_quota; } std::optional ContextAccess::getQuotaUsage() const { return getQuota()->getUsage(); } SettingsChanges ContextAccess::getDefaultSettings() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (enabled_settings) { if (auto info = enabled_settings->getInfo()) return info->settings; } return {}; } std::shared_ptr ContextAccess::getDefaultProfileInfo() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (enabled_settings) return enabled_settings->getInfo(); static const auto everything_by_default = std::make_shared(*access_control); return everything_by_default; } std::shared_ptr ContextAccess::getAccessRights() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (access) return access; static const auto nothing_granted = std::make_shared(); return nothing_granted; } std::shared_ptr ContextAccess::getAccessRightsWithImplicit() const { std::lock_guard lock{mutex}; if (initialized && !user && !user_was_dropped) throw Exception(ErrorCodes::LOGICAL_ERROR, "ContextAccess is inconsistent (bug 55041)"); if (access_with_implicit) return access_with_implicit; static const auto nothing_granted = std::make_shared(); return nothing_granted; } template bool ContextAccess::checkAccessImplHelper(AccessFlags flags, const Args &... args) const { if (user_was_dropped) { /// If the current user has been dropped we always throw an exception (even if `throw_if_denied` is false) /// because dropping of the current user is considered as a situation which is exceptional enough to stop /// query execution. throw Exception(ErrorCodes::UNKNOWN_USER, "{}: User has been dropped", getUserName()); } if (params.full_access) return true; auto access_granted = [&] { if (trace_log) LOG_TRACE(trace_log, "Access granted: {}{}", (AccessRightsElement{flags, args...}.toStringWithoutOptions()), (grant_option ? " WITH GRANT OPTION" : "")); return true; }; auto access_denied = [&](int error_code [[maybe_unused]], FormatStringHelper fmt_string [[maybe_unused]], FmtArgs && ...fmt_args [[maybe_unused]]) { if (trace_log) LOG_TRACE(trace_log, "Access denied: {}{}", (AccessRightsElement{flags, args...}.toStringWithoutOptions()), (grant_option ? " WITH GRANT OPTION" : "")); if constexpr (throw_if_denied) throw Exception(error_code, std::move(fmt_string), getUserName(), std::forward(fmt_args)...); return false; }; if (flags & AccessType::CLUSTER && !access_control->doesOnClusterQueriesRequireClusterGrant()) flags &= ~AccessType::CLUSTER; if (!flags) return true; const auto parameter_type = flags.getParameterType(); if (parameter_type == AccessFlags::NONE) { /// Access to temporary tables is controlled in an unusual way, not like normal tables. /// Creating of temporary tables is controlled by AccessType::CREATE_TEMPORARY_TABLES grant, /// and other grants are considered as always given. /// The DatabaseCatalog class won't resolve StorageID for temporary tables /// which shouldn't be accessed. if (getDatabase(args...) == DatabaseCatalog::TEMPORARY_DATABASE) return access_granted(); } auto acs = getAccessRightsWithImplicit(); bool granted; if constexpr (grant_option) granted = acs->hasGrantOption(flags, args...); else granted = acs->isGranted(flags, args...); if (!granted) { if (grant_option && acs->isGranted(flags, args...)) { return access_denied(ErrorCodes::ACCESS_DENIED, "{}: Not enough privileges. " "The required privileges have been granted, but without grant option. " "To execute this query, it's necessary to have the grant {} WITH GRANT OPTION", AccessRightsElement{flags, args...}.toStringWithoutOptions()); } return access_denied(ErrorCodes::ACCESS_DENIED, "{}: Not enough privileges. To execute this query, it's necessary to have the grant {}", AccessRightsElement{flags, args...}.toStringWithoutOptions() + (grant_option ? " WITH GRANT OPTION" : "")); } struct PrecalculatedFlags { const AccessFlags table_ddl = AccessType::CREATE_DATABASE | AccessType::CREATE_TABLE | AccessType::CREATE_VIEW | AccessType::ALTER_TABLE | AccessType::ALTER_VIEW | AccessType::DROP_DATABASE | AccessType::DROP_TABLE | AccessType::DROP_VIEW | AccessType::TRUNCATE; const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY; const AccessFlags function_ddl = AccessType::CREATE_FUNCTION | AccessType::DROP_FUNCTION; const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl; const AccessFlags table_and_dictionary_and_function_ddl = table_ddl | dictionary_ddl | function_ddl; const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE; const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS; const AccessFlags not_readonly_flags = write_table_access | table_and_dictionary_and_function_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY; const AccessFlags not_readonly_1_flags = AccessType::CREATE_TEMPORARY_TABLE; const AccessFlags ddl_flags = table_ddl | dictionary_ddl | function_ddl; const AccessFlags introspection_flags = AccessType::INTROSPECTION; }; static const PrecalculatedFlags precalc; if (params.readonly) { if constexpr (grant_option) return access_denied(ErrorCodes::READONLY, "{}: Cannot change grants in readonly mode."); if ((flags & precalc.not_readonly_flags) || ((params.readonly == 1) && (flags & precalc.not_readonly_1_flags))) { if (params.interface == ClientInfo::Interface::HTTP && params.http_method == ClientInfo::HTTPMethod::GET) { return access_denied(ErrorCodes::READONLY, "{}: Cannot execute query in readonly mode. " "For queries over HTTP, method GET implies readonly. " "You should use method POST for modifying queries"); } else return access_denied(ErrorCodes::READONLY, "{}: Cannot execute query in readonly mode"); } } if (!params.allow_ddl && !grant_option) { if (flags & precalc.ddl_flags) return access_denied(ErrorCodes::QUERY_IS_PROHIBITED, "Cannot execute query. DDL queries are prohibited for the user {}"); } if (!params.allow_introspection && !grant_option) { if (flags & precalc.introspection_flags) return access_denied(ErrorCodes::FUNCTION_NOT_ALLOWED, "{}: Introspection functions are disabled, " "because setting 'allow_introspection_functions' is set to 0"); } return access_granted(); } template bool ContextAccess::checkAccessImpl(const AccessFlags & flags) const { return checkAccessImplHelper(flags); } template bool ContextAccess::checkAccessImpl(const AccessFlags & flags, std::string_view database, const Args &... args) const { return checkAccessImplHelper(flags, database.empty() ? params.current_database : database, args...); } template bool ContextAccess::checkAccessImplHelper(const AccessRightsElement & element) const { assert(!element.grant_option || grant_option); if (element.isGlobalWithParameter()) { if (element.any_parameter) return checkAccessImpl(element.access_flags); else return checkAccessImpl(element.access_flags, element.parameter); } else if (element.any_database) return checkAccessImpl(element.access_flags); else if (element.any_table) return checkAccessImpl(element.access_flags, element.database); else if (element.any_column) return checkAccessImpl(element.access_flags, element.database, element.table); else return checkAccessImpl(element.access_flags, element.database, element.table, element.columns); } template bool ContextAccess::checkAccessImpl(const AccessRightsElement & element) const { if constexpr (grant_option) { return checkAccessImplHelper(element); } else { if (element.grant_option) return checkAccessImplHelper(element); else return checkAccessImplHelper(element); } } template bool ContextAccess::checkAccessImpl(const AccessRightsElements & elements) const { for (const auto & element : elements) if (!checkAccessImpl(element)) return false; return true; } bool ContextAccess::isGranted(const AccessFlags & flags) const { return checkAccessImpl(flags); } bool ContextAccess::isGranted(const AccessFlags & flags, std::string_view database) const { return checkAccessImpl(flags, database); } bool ContextAccess::isGranted(const AccessFlags & flags, std::string_view database, std::string_view table) const { return checkAccessImpl(flags, database, table); } bool ContextAccess::isGranted(const AccessFlags & flags, std::string_view database, std::string_view table, std::string_view column) const { return checkAccessImpl(flags, database, table, column); } bool ContextAccess::isGranted(const AccessFlags & flags, std::string_view database, std::string_view table, const std::vector & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::isGranted(const AccessFlags & flags, std::string_view database, std::string_view table, const Strings & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::isGranted(const AccessRightsElement & element) const { return checkAccessImpl(element); } bool ContextAccess::isGranted(const AccessRightsElements & elements) const { return checkAccessImpl(elements); } bool ContextAccess::hasGrantOption(const AccessFlags & flags) const { return checkAccessImpl(flags); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, std::string_view database) const { return checkAccessImpl(flags, database); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table) const { return checkAccessImpl(flags, database, table); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, std::string_view column) const { return checkAccessImpl(flags, database, table, column); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, const std::vector & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, const Strings & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::hasGrantOption(const AccessRightsElement & element) const { return checkAccessImpl(element); } bool ContextAccess::hasGrantOption(const AccessRightsElements & elements) const { return checkAccessImpl(elements); } void ContextAccess::checkAccess(const AccessFlags & flags) const { checkAccessImpl(flags); } void ContextAccess::checkAccess(const AccessFlags & flags, std::string_view database) const { checkAccessImpl(flags, database); } void ContextAccess::checkAccess(const AccessFlags & flags, std::string_view database, std::string_view table) const { checkAccessImpl(flags, database, table); } void ContextAccess::checkAccess(const AccessFlags & flags, std::string_view database, std::string_view table, std::string_view column) const { checkAccessImpl(flags, database, table, column); } void ContextAccess::checkAccess(const AccessFlags & flags, std::string_view database, std::string_view table, const std::vector & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkAccess(const AccessFlags & flags, std::string_view database, std::string_view table, const Strings & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkAccess(const AccessRightsElement & element) const { checkAccessImpl(element); } void ContextAccess::checkAccess(const AccessRightsElements & elements) const { checkAccessImpl(elements); } void ContextAccess::checkGrantOption(const AccessFlags & flags) const { checkAccessImpl(flags); } void ContextAccess::checkGrantOption(const AccessFlags & flags, std::string_view database) const { checkAccessImpl(flags, database); } void ContextAccess::checkGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table) const { checkAccessImpl(flags, database, table); } void ContextAccess::checkGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, std::string_view column) const { checkAccessImpl(flags, database, table, column); } void ContextAccess::checkGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, const std::vector & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkGrantOption(const AccessFlags & flags, std::string_view database, std::string_view table, const Strings & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkGrantOption(const AccessRightsElement & element) const { checkAccessImpl(element); } void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl(elements); } template bool ContextAccess::checkAdminOptionImplHelper(const Container & role_ids, const GetNameFunction & get_name_function) const { auto show_error = [](int error_code [[maybe_unused]], FormatStringHelper fmt_string [[maybe_unused]], FmtArgs && ...fmt_args [[maybe_unused]]) { if constexpr (throw_if_denied) throw Exception(error_code, std::move(fmt_string), std::forward(fmt_args)...); return false; }; if (params.full_access) return true; if (user_was_dropped) { show_error(ErrorCodes::UNKNOWN_USER, "User has been dropped"); return false; } if (!std::size(role_ids)) return true; if (isGranted(AccessType::ROLE_ADMIN)) return true; auto info = getRolesInfo(); size_t i = 0; for (auto it = std::begin(role_ids); it != std::end(role_ids); ++it, ++i) { const UUID & role_id = *it; if (info->enabled_roles_with_admin_option.count(role_id)) continue; if (throw_if_denied) { auto role_name = get_name_function(role_id, i); if (!role_name) role_name = "ID {" + toString(role_id) + "}"; if (info->enabled_roles.count(role_id)) show_error(ErrorCodes::ACCESS_DENIED, "Not enough privileges. " "Role {} is granted, but without ADMIN option. " "To execute this query, it's necessary to have the role {} granted with ADMIN option.", backQuote(*role_name), backQuoteIfNeed(*role_name)); else show_error(ErrorCodes::ACCESS_DENIED, "Not enough privileges. " "To execute this query, it's necessary to have the role {} granted with ADMIN option.", backQuoteIfNeed(*role_name)); } return false; } return true; } template bool ContextAccess::checkAdminOptionImpl(const UUID & role_id) const { return checkAdminOptionImplHelper(to_array(role_id), [this](const UUID & id, size_t) { return access_control->tryReadName(id); }); } template bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const String & role_name) const { return checkAdminOptionImplHelper(to_array(role_id), [&role_name](const UUID &, size_t) { return std::optional{role_name}; }); } template bool ContextAccess::checkAdminOptionImpl(const UUID & role_id, const std::unordered_map & names_of_roles) const { return checkAdminOptionImplHelper(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{}; }); } template bool ContextAccess::checkAdminOptionImpl(const std::vector & role_ids) const { return checkAdminOptionImplHelper(role_ids, [this](const UUID & id, size_t) { return access_control->tryReadName(id); }); } template bool ContextAccess::checkAdminOptionImpl(const std::vector & role_ids, const Strings & names_of_roles) const { return checkAdminOptionImplHelper(role_ids, [&names_of_roles](const UUID &, size_t i) { return std::optional{names_of_roles[i]}; }); } template bool ContextAccess::checkAdminOptionImpl(const std::vector & role_ids, const std::unordered_map & names_of_roles) const { return checkAdminOptionImplHelper(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{}; }); } bool ContextAccess::hasAdminOption(const UUID & role_id) const { return checkAdminOptionImpl(role_id); } bool ContextAccess::hasAdminOption(const UUID & role_id, const String & role_name) const { return checkAdminOptionImpl(role_id, role_name); } bool ContextAccess::hasAdminOption(const UUID & role_id, const std::unordered_map & names_of_roles) const { return checkAdminOptionImpl(role_id, names_of_roles); } bool ContextAccess::hasAdminOption(const std::vector & role_ids) const { return checkAdminOptionImpl(role_ids); } bool ContextAccess::hasAdminOption(const std::vector & role_ids, const Strings & names_of_roles) const { return checkAdminOptionImpl(role_ids, names_of_roles); } bool ContextAccess::hasAdminOption(const std::vector & role_ids, const std::unordered_map & names_of_roles) const { return checkAdminOptionImpl(role_ids, names_of_roles); } void ContextAccess::checkAdminOption(const UUID & role_id) const { checkAdminOptionImpl(role_id); } void ContextAccess::checkAdminOption(const UUID & role_id, const String & role_name) const { checkAdminOptionImpl(role_id, role_name); } void ContextAccess::checkAdminOption(const UUID & role_id, const std::unordered_map & names_of_roles) const { checkAdminOptionImpl(role_id, names_of_roles); } void ContextAccess::checkAdminOption(const std::vector & role_ids) const { checkAdminOptionImpl(role_ids); } void ContextAccess::checkAdminOption(const std::vector & role_ids, const Strings & names_of_roles) const { checkAdminOptionImpl(role_ids, names_of_roles); } void ContextAccess::checkAdminOption(const std::vector & role_ids, const std::unordered_map & names_of_roles) const { checkAdminOptionImpl(role_ids, names_of_roles); } void ContextAccess::checkGranteeIsAllowed(const UUID & grantee_id, const IAccessEntity & grantee) const { if (params.full_access) return; auto current_user = getUser(); if (!current_user->grantees.match(grantee_id)) throw Exception(ErrorCodes::ACCESS_DENIED, "{} is not allowed as grantee", grantee.formatTypeWithName()); } void ContextAccess::checkGranteesAreAllowed(const std::vector & grantee_ids) const { if (params.full_access) return; auto current_user = getUser(); if (current_user->grantees == RolesOrUsersSet::AllTag{}) return; for (const auto & id : grantee_ids) { auto entity = access_control->tryRead(id); if (auto role_entity = typeid_cast(entity)) checkGranteeIsAllowed(id, *role_entity); else if (auto user_entity = typeid_cast(entity)) checkGranteeIsAllowed(id, *user_entity); } } }