diff --git a/dbms/src/Access/AccessRightsContext.cpp b/dbms/src/Access/AccessRightsContext.cpp index 23f7382e8c220cca4245b2dd9bc8e5ae4f5e8d79..5c239feee154f241897f2464abfdba62a5841061 100644 --- a/dbms/src/Access/AccessRightsContext.cpp +++ b/dbms/src/Access/AccessRightsContext.cpp @@ -102,10 +102,10 @@ AccessRightsContext::AccessRightsContext(const UserPtr & user_, const ClientInfo } -template +template bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessFlags & access, const Args &... args) const { - auto result_access = calculateResultAccess(); + auto result_access = calculateResultAccess(grant_option); bool is_granted = result_access->isGranted(access, args...); if (trace_log) @@ -131,7 +131,16 @@ bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessFlags & acc LOG_WARNING(log_, user->getName() + ": " + msg + formatSkippedMessage(args...)); }; - if (readonly && calculateResultAccess(false, allow_ddl, allow_introspection)->isGranted(access, args...)) + if (grant_option && calculateResultAccess(false, readonly, allow_ddl, allow_introspection)->isGranted(access, args...)) + { + show_error( + "Not enough privileges. " + "The required privileges have been granted, but without grant option. " + "To execute this query it's necessary to have the grant " + + AccessRightsElement{access, args...}.toString() + " WITH GRANT OPTION", + ErrorCodes::ACCESS_DENIED); + } + else if (readonly && calculateResultAccess(false, false, allow_ddl, allow_introspection)->isGranted(access, args...)) { if (interface == ClientInfo::Interface::HTTP && http_method == ClientInfo::HTTPMethod::GET) show_error( @@ -141,11 +150,11 @@ bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessFlags & acc else show_error("Cannot execute query in readonly mode", ErrorCodes::READONLY); } - else if (!allow_ddl && calculateResultAccess(readonly, true, allow_introspection)->isGranted(access, args...)) + else if (!allow_ddl && calculateResultAccess(false, readonly, true, allow_introspection)->isGranted(access, args...)) { show_error("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED); } - else if (!allow_introspection && calculateResultAccess(readonly, allow_ddl, true)->isGranted(access, args...)) + else if (!allow_introspection && calculateResultAccess(false, readonly, allow_ddl, true)->isGranted(access, args...)) { show_error("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED); } @@ -153,7 +162,7 @@ bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessFlags & acc { show_error( "Not enough privileges. To execute this query it's necessary to have the grant " - + AccessRightsElement{access, args...}.toString(), + + AccessRightsElement{access, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""), ErrorCodes::ACCESS_DENIED); } @@ -161,86 +170,96 @@ bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessFlags & acc } -template +template bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessRightsElement & element) const { if (element.any_database) { - return checkImpl(log_, element.access_flags); + return checkImpl(log_, element.access_flags); } else if (element.any_table) { if (element.database.empty()) - return checkImpl(log_, element.access_flags, current_database); + return checkImpl(log_, element.access_flags, current_database); else - return checkImpl(log_, element.access_flags, element.database); + return checkImpl(log_, element.access_flags, element.database); } else if (element.any_column) { if (element.database.empty()) - return checkImpl(log_, element.access_flags, current_database, element.table); + return checkImpl(log_, element.access_flags, current_database, element.table); else - return checkImpl(log_, element.access_flags, element.database, element.table); + return checkImpl(log_, element.access_flags, element.database, element.table); } else { if (element.database.empty()) - return checkImpl(log_, element.access_flags, current_database, element.table, element.columns); + return checkImpl(log_, element.access_flags, current_database, element.table, element.columns); else - return checkImpl(log_, element.access_flags, element.database, element.table, element.columns); + return checkImpl(log_, element.access_flags, element.database, element.table, element.columns); } } -template +template bool AccessRightsContext::checkImpl(Poco::Logger * log_, const AccessRightsElements & elements) const { for (const auto & element : elements) - if (!checkImpl(log_, element)) + if (!checkImpl(log_, element)) return false; return true; } -void AccessRightsContext::check(const AccessFlags & access) const { checkImpl(nullptr, access); } -void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database) const { checkImpl(nullptr, access, database); } -void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { checkImpl(nullptr, access, database, table); } -void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkImpl(nullptr, access, database, table, column); } -void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { checkImpl(nullptr, access, database, table, columns); } -void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkImpl(nullptr, access, database, table, columns); } -void AccessRightsContext::check(const AccessRightsElement & access) const { checkImpl(nullptr, access); } -void AccessRightsContext::check(const AccessRightsElements & access) const { checkImpl(nullptr, access); } - -bool AccessRightsContext::isGranted(const AccessFlags & access) const { return checkImpl(nullptr, access); } -bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database) const { return checkImpl(nullptr, access, database); } -bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { return checkImpl(nullptr, access, database, table); } -bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkImpl(nullptr, access, database, table, column); } -bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkImpl(nullptr, access, database, table, columns); } -bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkImpl(nullptr, access, database, table, columns); } -bool AccessRightsContext::isGranted(const AccessRightsElement & access) const { return checkImpl(nullptr, access); } -bool AccessRightsContext::isGranted(const AccessRightsElements & access) const { return checkImpl(nullptr, access); } - -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access) const { return checkImpl(log_, access); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database) const { return checkImpl(log_, access, database); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { return checkImpl(log_, access, database, table); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkImpl(log_, access, database, table, column); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkImpl(log_, access, database, table, columns); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkImpl(log_, access, database, table, columns); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessRightsElement & access) const { return checkImpl(log_, access); } -bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessRightsElements & access) const { return checkImpl(log_, access); } - - -boost::shared_ptr AccessRightsContext::calculateResultAccess() const +void AccessRightsContext::check(const AccessFlags & access) const { checkImpl(nullptr, access); } +void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database) const { checkImpl(nullptr, access, database); } +void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { checkImpl(nullptr, access, database, table); } +void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkImpl(nullptr, access, database, table, column); } +void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { checkImpl(nullptr, access, database, table, columns); } +void AccessRightsContext::check(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkImpl(nullptr, access, database, table, columns); } +void AccessRightsContext::check(const AccessRightsElement & access) const { checkImpl(nullptr, access); } +void AccessRightsContext::check(const AccessRightsElements & access) const { checkImpl(nullptr, access); } + +bool AccessRightsContext::isGranted(const AccessFlags & access) const { return checkImpl(nullptr, access); } +bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database) const { return checkImpl(nullptr, access, database); } +bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { return checkImpl(nullptr, access, database, table); } +bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkImpl(nullptr, access, database, table, column); } +bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkImpl(nullptr, access, database, table, columns); } +bool AccessRightsContext::isGranted(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkImpl(nullptr, access, database, table, columns); } +bool AccessRightsContext::isGranted(const AccessRightsElement & access) const { return checkImpl(nullptr, access); } +bool AccessRightsContext::isGranted(const AccessRightsElements & access) const { return checkImpl(nullptr, access); } + +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access) const { return checkImpl(log_, access); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database) const { return checkImpl(log_, access, database); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { return checkImpl(log_, access, database, table); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkImpl(log_, access, database, table, column); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { return checkImpl(log_, access, database, table, columns); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkImpl(log_, access, database, table, columns); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessRightsElement & access) const { return checkImpl(log_, access); } +bool AccessRightsContext::isGranted(Poco::Logger * log_, const AccessRightsElements & access) const { return checkImpl(log_, access); } + +void AccessRightsContext::checkGrantOption(const AccessFlags & access) const { checkImpl(nullptr, access); } +void AccessRightsContext::checkGrantOption(const AccessFlags & access, const std::string_view & database) const { checkImpl(nullptr, access, database); } +void AccessRightsContext::checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const { checkImpl(nullptr, access, database, table); } +void AccessRightsContext::checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkImpl(nullptr, access, database, table, column); } +void AccessRightsContext::checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const { checkImpl(nullptr, access, database, table, columns); } +void AccessRightsContext::checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkImpl(nullptr, access, database, table, columns); } +void AccessRightsContext::checkGrantOption(const AccessRightsElement & access) const { checkImpl(nullptr, access); } +void AccessRightsContext::checkGrantOption(const AccessRightsElements & access) const { checkImpl(nullptr, access); } + + +boost::shared_ptr AccessRightsContext::calculateResultAccess(bool grant_option) const { - return calculateResultAccess(readonly, allow_ddl, allow_introspection); + return calculateResultAccess(grant_option, readonly, allow_ddl, allow_introspection); } -boost::shared_ptr AccessRightsContext::calculateResultAccess(UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const +boost::shared_ptr AccessRightsContext::calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const { size_t cache_index = static_cast(readonly_ != readonly) + static_cast(allow_ddl_ != allow_ddl) * 2 + - + static_cast(allow_introspection_ != allow_introspection) * 3; + + static_cast(allow_introspection_ != allow_introspection) * 3 + + static_cast(grant_option) * 4; assert(cache_index < std::size(result_access_cache)); auto cached = result_access_cache[cache_index].load(); if (cached) @@ -254,7 +273,7 @@ boost::shared_ptr AccessRightsContext::calculateResultAccess auto result_ptr = boost::make_shared(); auto & result = *result_ptr; - result = user->access; + result = grant_option ? user->access_with_grant_option : user->access; static 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 @@ -263,12 +282,18 @@ boost::shared_ptr AccessRightsContext::calculateResultAccess static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl; static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE; + /// Anyone has access to the "system" database. + result.grant(AccessType::SELECT, "system"); + if (readonly_) result.fullRevoke(write_table_access | AccessType::SYSTEM); if (readonly_ || !allow_ddl_) result.fullRevoke(table_and_dictionary_ddl); + if (readonly_ && grant_option) + result.fullRevoke(AccessType::ALL); + if (readonly_ == 1) { /// Table functions are forbidden in readonly mode. @@ -282,7 +307,7 @@ boost::shared_ptr AccessRightsContext::calculateResultAccess result_access_cache[cache_index].store(result_ptr); if (trace_log && (readonly == readonly_) && (allow_ddl == allow_ddl_) && (allow_introspection == allow_introspection_)) - LOG_TRACE(trace_log, "List of all grants: " << result_ptr->toString()); + LOG_TRACE(trace_log, "List of all grants: " << result_ptr->toString() << (grant_option ? " WITH GRANT OPTION" : "")); return result_ptr; } diff --git a/dbms/src/Access/AccessRightsContext.h b/dbms/src/Access/AccessRightsContext.h index afc7bff82b2a129e629f8f8d318765b24bfafd89..2c87ce5767481620520c581fda0de5377a1a1605 100644 --- a/dbms/src/Access/AccessRightsContext.h +++ b/dbms/src/Access/AccessRightsContext.h @@ -54,18 +54,28 @@ public: bool isGranted(Poco::Logger * log_, const AccessRightsElement & access) const; bool isGranted(Poco::Logger * log_, const AccessRightsElements & access) const; + /// Checks if a specified access granted with grant option, and throws an exception if not. + void checkGrantOption(const AccessFlags & access) const; + void checkGrantOption(const AccessFlags & access, const std::string_view & database) const; + void checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table) const; + void checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::string_view & column) const; + void checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const std::vector & columns) const; + void checkGrantOption(const AccessFlags & access, const std::string_view & database, const std::string_view & table, const Strings & columns) const; + void checkGrantOption(const AccessRightsElement & access) const; + void checkGrantOption(const AccessRightsElements & access) const; + private: - template + template bool checkImpl(Poco::Logger * log_, const AccessFlags & access, const Args &... args) const; - template + template bool checkImpl(Poco::Logger * log_, const AccessRightsElement & access) const; - template + template bool checkImpl(Poco::Logger * log_, const AccessRightsElements & access) const; - boost::shared_ptr calculateResultAccess() const; - boost::shared_ptr calculateResultAccess(UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const; + boost::shared_ptr calculateResultAccess(bool grant_option) const; + boost::shared_ptr calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const; const UserPtr user; const UInt64 readonly = 0; @@ -75,7 +85,7 @@ private: const ClientInfo::Interface interface = ClientInfo::Interface::TCP; const ClientInfo::HTTPMethod http_method = ClientInfo::HTTPMethod::UNKNOWN; Poco::Logger * const trace_log = nullptr; - mutable boost::atomic_shared_ptr result_access_cache[4]; + mutable boost::atomic_shared_ptr result_access_cache[7]; mutable std::mutex mutex; }; diff --git a/dbms/src/Access/User.cpp b/dbms/src/Access/User.cpp index af84e14d9d8ead8b93aef63a263f3f32d247e863..2efe7ed10763c0b2742360ef8fd2cc7be76a1d2c 100644 --- a/dbms/src/Access/User.cpp +++ b/dbms/src/Access/User.cpp @@ -10,7 +10,8 @@ bool User::equal(const IAccessEntity & other) const return false; const auto & other_user = typeid_cast(other); return (authentication == other_user.authentication) && (allowed_client_hosts == other_user.allowed_client_hosts) - && (access == other_user.access) && (profile == other_user.profile); + && (access == other_user.access) && (access_with_grant_option == other_user.access_with_grant_option) + && (profile == other_user.profile); } } diff --git a/dbms/src/Access/User.h b/dbms/src/Access/User.h index 0aa33c3696a1576189e76e52db55e6946dbef83d..9db9a8bcf4ad7d3f584a040a52bdebe078043e3c 100644 --- a/dbms/src/Access/User.h +++ b/dbms/src/Access/User.h @@ -16,6 +16,7 @@ struct User : public IAccessEntity Authentication authentication; AllowedClientHosts allowed_client_hosts{AllowedClientHosts::AnyHostTag{}}; AccessRights access; + AccessRights access_with_grant_option; String profile; bool equal(const IAccessEntity & other) const override; diff --git a/dbms/src/Access/UsersConfigAccessStorage.cpp b/dbms/src/Access/UsersConfigAccessStorage.cpp index adf96a38037031324a23b436e7792c7ff87c0a8b..e71b2c27fa59179c747b4a1315aef36b9f9990b1 100644 --- a/dbms/src/Access/UsersConfigAccessStorage.cpp +++ b/dbms/src/Access/UsersConfigAccessStorage.cpp @@ -144,7 +144,6 @@ namespace user->access.fullRevoke(AccessFlags::databaseLevel()); for (const String & database : *databases) user->access.grant(AccessFlags::databaseLevel(), database); - user->access.grant(AccessFlags::databaseLevel(), "system"); /// Anyone has access to the "system" database. } if (dictionaries) @@ -156,6 +155,8 @@ namespace else if (databases) user->access.grant(AccessType::dictGet, IDictionary::NO_DATABASE_TAG); + user->access_with_grant_option = user->access; + return user; }