#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; } 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) { auto modifier = [&](const AccessFlags & flags, const AccessFlags & min_flags_with_children, const AccessFlags & max_flags_with_children, const std::string_view & database, const std::string_view & table, const std::string_view & column) -> 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; /// 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; } return res; }; AccessRights res = access; res.modifyFlags(modifier); /// Anyone has access to the "system" database. res.grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE); 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(const std::string_view & arg1, const OtherArgs &...) { return arg1; } } ContextAccess::ContextAccess(const AccessControlManager & manager_, const Params & params_) : manager(&manager_) , params(params_) { std::lock_guard lock{mutex}; subscription_for_user_change = manager->subscribeForChanges( *params.user_id, [this](const UUID &, const AccessEntityPtr & entity) { UserPtr changed_user = entity ? typeid_cast(entity) : nullptr; std::lock_guard lock2{mutex}; setUser(changed_user); }); setUser(manager->read(*params.user_id)); } void ContextAccess::setUser(const UserPtr & user_) const { user = user_; if (!user) { /// User has been dropped. subscription_for_user_change = {}; subscription_for_roles_changes = {}; access = nullptr; access_with_implicit = nullptr; enabled_roles = nullptr; roles_info = nullptr; enabled_row_policies = nullptr; enabled_quota = nullptr; enabled_settings = nullptr; return; } user_name = user->getName(); trace_log = &Poco::Logger::get("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 { 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 = manager->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()); } void ContextAccess::setRolesInfo(const std::shared_ptr & roles_info_) const { assert(roles_info_); roles_info = roles_info_; enabled_row_policies = manager->getEnabledRowPolicies( *params.user_id, roles_info->enabled_roles); enabled_quota = manager->getEnabledQuota( *params.user_id, user_name, roles_info->enabled_roles, params.address, params.forwarded_address, params.quota_key); enabled_settings = manager->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)); 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 { std::lock_guard lock{mutex}; return user; } String ContextAccess::getUserName() const { std::lock_guard lock{mutex}; return user_name; } std::shared_ptr ContextAccess::getRolesInfo() const { std::lock_guard lock{mutex}; if (roles_info) return roles_info; static const auto no_roles = std::make_shared(); return no_roles; } std::shared_ptr ContextAccess::getEnabledRowPolicies() const { std::lock_guard lock{mutex}; if (enabled_row_policies) return enabled_row_policies; static const auto no_row_policies = std::make_shared(); return no_row_policies; } ASTPtr ContextAccess::getRowPolicyCondition(const String & database, const String & table_name, RowPolicy::ConditionType index, const ASTPtr & extra_condition) const { std::lock_guard lock{mutex}; if (enabled_row_policies) return enabled_row_policies->getCondition(database, table_name, index, extra_condition); return nullptr; } std::shared_ptr ContextAccess::getQuota() const { std::lock_guard lock{mutex}; if (enabled_quota) return enabled_quota; static const auto unlimited_quota = EnabledQuota::getUnlimitedQuota(); return unlimited_quota; } std::optional ContextAccess::getQuotaUsage() const { std::lock_guard lock{mutex}; if (enabled_quota) return enabled_quota->getUsage(); return {}; } std::shared_ptr ContextAccess::getFullAccess() { static const std::shared_ptr res = [] { auto full_access = std::shared_ptr(new ContextAccess); full_access->is_full_access = true; full_access->access = std::make_shared(AccessRights::getFullAccess()); full_access->access_with_implicit = std::make_shared(addImplicitAccessRights(*full_access->access)); return full_access; }(); return res; } std::shared_ptr ContextAccess::getDefaultSettings() const { std::lock_guard lock{mutex}; if (enabled_settings) return enabled_settings->getSettings(); static const auto everything_by_default = std::make_shared(); return everything_by_default; } std::shared_ptr ContextAccess::getSettingsConstraints() const { std::lock_guard lock{mutex}; if (enabled_settings) return enabled_settings->getConstraints(); static const auto no_constraints = std::make_shared(); return no_constraints; } std::shared_ptr ContextAccess::getAccessRights() const { std::lock_guard lock{mutex}; 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 (access_with_implicit) return access_with_implicit; static const auto nothing_granted = std::make_shared(); return nothing_granted; } Strings ContextAccess::getCurrentProfileNames() const { std::lock_guard lock{mutex}; Strings result; if (!enabled_settings) return result; const auto & profiles = enabled_settings->getCurrentProfiles(); Strings profile_names; profile_names.reserve(profiles.size()); for (const auto & profile_id : profiles) profile_names.emplace_back(manager->getProfileName(profile_id)); return profile_names; } template 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...}.toStringWithoutOptions()), (grant_option ? " WITH GRANT OPTION" : "")); return true; }; auto access_denied = [&](const String & error_msg, int error_code [[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(getUserName() + ": " + error_msg, error_code); return false; }; if (!flags || is_full_access) return access_granted(); if (!getUser()) return access_denied("User has been dropped", ErrorCodes::UNKNOWN_USER); /// 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( "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...}.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...}.toStringWithoutOptions() + (grant_option ? " WITH GRANT OPTION" : ""), ErrorCodes::ACCESS_DENIED); } 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 table_and_dictionary_ddl = table_ddl | dictionary_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_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; const AccessFlags introspection_flags = AccessType::INTROSPECTION; }; static const PrecalculatedFlags precalc; if (params.readonly) { if constexpr (grant_option) return access_denied("Cannot change grants in readonly mode.", ErrorCodes::READONLY); 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( "Cannot execute query in readonly mode. " "For queries over HTTP, method GET implies readonly. You should use method POST for modifying queries", ErrorCodes::READONLY); } else return access_denied("Cannot execute query in readonly mode", ErrorCodes::READONLY); } } if (!params.allow_ddl && !grant_option) { if (flags & precalc.ddl_flags) return access_denied("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED); } if (!params.allow_introspection && !grant_option) { if (flags & precalc.introspection_flags) return access_denied("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED); } return access_granted(); } template bool ContextAccess::checkAccessImpl(const AccessFlags & flags) const { return checkAccessImplHelper(flags); } template bool ContextAccess::checkAccessImpl(const AccessFlags & flags, const 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.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, const std::string_view & database) const { return checkAccessImpl(flags, database); } bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl(flags, database, table); } bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl(flags, database, table, column); } bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkAccessImpl(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, const std::string_view & database) const { return checkAccessImpl(flags, database); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl(flags, database, table); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl(flags, database, table, column); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkAccessImpl(flags, database, table, columns); } bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return 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, const std::string_view & database) const { checkAccessImpl(flags, database); } void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl(flags, database, table); } void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl(flags, database, table, column); } void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl(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, const std::string_view & database) const { checkAccessImpl(flags, database); } void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl(flags, database, table); } void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl(flags, database, table, column); } void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { checkAccessImpl(flags, database, table, columns); } void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl(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 { if (!std::size(role_ids) || is_full_access) return true; auto show_error = [this](const String & msg, int error_code [[maybe_unused]]) { UNUSED(this); if constexpr (throw_if_denied) throw Exception(getUserName() + ": " + msg, error_code); }; if (!getUser()) { show_error("User has been dropped", ErrorCodes::UNKNOWN_USER); return false; } 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("Not enough privileges. " "Role " + backQuote(*role_name) + " is granted, but without ADMIN option. " "To execute this query it's necessary to have the role " + backQuoteIfNeed(*role_name) + " granted with ADMIN option.", ErrorCodes::ACCESS_DENIED); else show_error("Not enough privileges. " "To execute this query it's necessary to have the role " + backQuoteIfNeed(*role_name) + " granted with ADMIN option.", ErrorCodes::ACCESS_DENIED); } 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 manager->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 manager->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); } }