From 5579d9f45e2207ab41b07af35f9d24188a1f9ce5 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Sat, 28 Sep 2024 17:08:47 +0000 Subject: [PATCH] Backport #69563 to 24.9: Improve restoring of access entities' dependencies #2 --- src/Access/AccessBackup.cpp | 757 ++++++++++++------ src/Access/AccessBackup.h | 114 ++- src/Access/AccessControl.cpp | 4 +- src/Access/AccessControl.h | 2 +- src/Access/DiskAccessStorage.cpp | 2 +- src/Access/GrantedRoles.cpp | 62 ++ src/Access/GrantedRoles.h | 3 + src/Access/IAccessEntity.cpp | 24 - src/Access/IAccessEntity.h | 9 +- src/Access/IAccessStorage.cpp | 105 +-- src/Access/IAccessStorage.h | 23 +- src/Access/LDAPAccessStorage.cpp | 4 +- src/Access/MemoryAccessStorage.cpp | 2 +- src/Access/MultipleAccessStorage.cpp | 6 +- src/Access/MultipleAccessStorage.h | 2 +- src/Access/Quota.cpp | 26 +- src/Access/Quota.h | 7 +- src/Access/ReplicatedAccessStorage.cpp | 46 +- src/Access/ReplicatedAccessStorage.h | 4 +- src/Access/Role.cpp | 28 +- src/Access/Role.h | 7 +- src/Access/RolesOrUsersSet.cpp | 54 ++ src/Access/RolesOrUsersSet.h | 3 + src/Access/RowPolicy.cpp | 26 +- src/Access/RowPolicy.h | 7 +- src/Access/SettingsProfile.cpp | 27 +- src/Access/SettingsProfile.h | 7 +- src/Access/SettingsProfileElement.cpp | 44 + src/Access/SettingsProfileElement.h | 5 + src/Access/User.cpp | 36 +- src/Access/User.h | 7 +- src/Backups/BackupCoordinationRemote.cpp | 2 +- .../BackupCoordinationReplicatedAccess.cpp | 19 +- .../BackupCoordinationReplicatedAccess.h | 8 +- src/Backups/BackupCoordinationStage.h | 3 + src/Backups/BackupEntriesCollector.cpp | 16 +- src/Backups/BackupEntriesCollector.h | 12 +- src/Backups/BackupSettings.cpp | 1 + src/Backups/BackupSettings.h | 5 + src/Backups/RestoreSettings.cpp | 10 +- src/Backups/RestoreSettings.h | 22 +- src/Backups/RestorerFromBackup.cpp | 64 +- src/Backups/RestorerFromBackup.h | 6 +- .../Access/InterpreterCreateQuotaQuery.cpp | 2 +- .../Access/InterpreterCreateRoleQuery.cpp | 2 +- .../InterpreterCreateRowPolicyQuery.cpp | 2 +- .../InterpreterCreateSettingsProfileQuery.cpp | 2 +- .../Access/InterpreterCreateUserQuery.cpp | 4 +- .../Access/InterpreterGrantQuery.cpp | 2 +- .../Access/InterpreterSetRoleQuery.cpp | 2 +- src/Storages/System/StorageSystemQuotas.cpp | 4 +- src/Storages/System/StorageSystemRoles.cpp | 4 +- .../System/StorageSystemRowPolicies.cpp | 4 +- .../System/StorageSystemSettingsProfiles.cpp | 4 +- src/Storages/System/StorageSystemUsers.cpp | 4 +- ...thout_access_entities_dependents.reference | 7 + ...ckup_without_access_entities_dependents.sh | 30 + ..._restore_user_with_existing_role.reference | 7 + .../03231_restore_user_with_existing_role.sh | 32 +- ...kup_without_access_entities_dependents.zip | Bin 0 -> 1996 bytes 60 files changed, 1232 insertions(+), 501 deletions(-) create mode 100644 tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.reference create mode 100755 tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.sh create mode 100644 tests/queries/0_stateless/backups/old_backup_without_access_entities_dependents.zip diff --git a/src/Access/AccessBackup.cpp b/src/Access/AccessBackup.cpp index e8ea21852b5..a24f8c5b2ad 100644 --- a/src/Access/AccessBackup.cpp +++ b/src/Access/AccessBackup.cpp @@ -30,6 +30,7 @@ namespace ErrorCodes { extern const int CANNOT_RESTORE_TABLE; extern const int ACCESS_ENTITY_ALREADY_EXISTS; + extern const int ACCESS_ENTITY_NOT_FOUND; extern const int LOGICAL_ERROR; } @@ -41,6 +42,7 @@ namespace { std::unordered_map entities; std::unordered_map> dependencies; + std::unordered_map dependents; BackupEntryPtr toBackupEntry() const { @@ -72,6 +74,24 @@ namespace } } + if (!dependents.empty()) + { + if (!dependencies.empty()) + writeText("\n", buf); + writeText("DEPENDENTS\n", buf); + for (const auto & [id, entity] : dependents) + { + writeText(id, buf); + writeChar('\t', buf); + writeText(entity->getTypeInfo().name, buf); + writeChar('\t', buf); + writeText(entity->getName(), buf); + writeChar('\n', buf); + writeText(serializeAccessEntity(*entity), buf); + writeChar('\n', buf); + } + } + return std::make_shared(buf.str()); } @@ -81,59 +101,71 @@ namespace { AccessEntitiesInBackup res; - bool dependencies_found = false; + bool reading_dependencies = false; + bool reading_dependents = false; while (!buf->eof()) { String line; readStringUntilNewlineInto(line, *buf); buf->ignore(); + if (line == "DEPENDENCIES") { - dependencies_found = true; - break; + reading_dependencies = true; + reading_dependents = false; + continue; + } + else if (line == "DEPENDENTS") + { + reading_dependents = true; + reading_dependencies = false; + continue; + } + else if (line.empty()) + { + continue; } - UUID id = parse(line.substr(0, line.find('\t'))); - line.clear(); + size_t separator1 = line.find('\t'); + size_t separator2 = line.find('\t', separator1 + 1); + if ((separator1 == String::npos) || (separator2 == String::npos)) + throw Exception(ErrorCodes::CANNOT_RESTORE_TABLE, "Separators not found in line {}", line); - String queries; - while (!buf->eof()) + UUID id = parse(line.substr(0, separator1)); + AccessEntityType type = AccessEntityTypeInfo::parseType(line.substr(separator1 + 1, separator2 - separator1 - 1)); + String name = line.substr(separator2 + 1); + + if (reading_dependencies) { - String query; - readStringUntilNewlineInto(query, *buf); - buf->ignore(); - if (query.empty()) - break; - if (!queries.empty()) - queries.append("\n"); - queries.append(query); + res.dependencies.emplace(id, std::pair{name, type}); } - - AccessEntityPtr entity = deserializeAccessEntity(queries); - res.entities.emplace(id, entity); - } - - if (dependencies_found) - { - while (!buf->eof()) + else { - String id_as_string; - readStringInto(id_as_string, *buf); - buf->ignore(); - UUID id = parse(id_as_string); + String queries; + while (!buf->eof()) + { + String query; + readStringUntilNewlineInto(query, *buf); + buf->ignore(); + if (query.empty()) + break; + if (!queries.empty()) + queries.append("\n"); + queries.append(query); + } - String type_as_string; - readStringInto(type_as_string, *buf); - buf->ignore(); - AccessEntityType type = AccessEntityTypeInfo::parseType(type_as_string); + AccessEntityPtr entity = deserializeAccessEntity(queries); - String name; - readStringInto(name, *buf); - buf->ignore(); + if (name != entity->getName()) + throw Exception(ErrorCodes::CANNOT_RESTORE_TABLE, "Unexpected name {} is specified for {}", name, entity->formatTypeWithName()); + if (type != entity->getType()) + throw Exception(ErrorCodes::CANNOT_RESTORE_TABLE, "Unexpected type {} is specified for {}", AccessEntityTypeInfo::get(type).name, entity->formatTypeWithName()); - if (!res.entities.contains(id)) - res.dependencies.emplace(id, std::pair{name, type}); + if (reading_dependents) + res.dependents.emplace(id, entity); + else + res.entities.emplace(id, entity); } } @@ -146,190 +178,59 @@ namespace } } }; - - std::vector findDependencies(const std::vector> & entities) - { - std::vector res; - for (const auto & entity : entities | boost::adaptors::map_values) - insertAtEnd(res, entity->findDependencies()); - - /// Remove duplicates in the list of dependencies (some entities can refer to other entities). - ::sort(res.begin(), res.end()); - res.erase(std::unique(res.begin(), res.end()), res.end()); - for (const auto & id : entities | boost::adaptors::map_keys) - { - auto it = std::lower_bound(res.begin(), res.end(), id); - if ((it != res.end()) && (*it == id)) - res.erase(it); - } - return res; - } - - std::unordered_map> readDependenciesNamesAndTypes(const std::vector & dependencies, const AccessControl & access_control) - { - std::unordered_map> res; - for (const auto & id : dependencies) - { - if (auto name_and_type = access_control.tryReadNameWithType(id)) - res.emplace(id, name_and_type.value()); - } - return res; - } - - /// Checks if new entities (which we're going to restore) already exist, - /// and either skips them or throws an exception depending on the restore settings. - void checkExistingEntities(std::vector> & entities, - std::unordered_map & old_to_new_id, - const AccessControl & access_control, - RestoreAccessCreationMode creation_mode) - { - if (creation_mode == RestoreAccessCreationMode::kReplace) - return; - - auto should_skip = [&](const std::pair & id_and_entity) - { - const auto & id = id_and_entity.first; - const auto & entity = *id_and_entity.second; - auto existing_id = access_control.find(entity.getType(), entity.getName()); - if (!existing_id) - { - return false; - } - else if (creation_mode == RestoreAccessCreationMode::kCreateIfNotExists) - { - old_to_new_id[id] = *existing_id; - return true; - } - else - { - throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Cannot restore {} because it already exists", entity.formatTypeWithName()); - } - }; - - std::erase_if(entities, should_skip); - } - - /// If new entities (which we're going to restore) depend on other entities which are not going to be restored or not present in the backup - /// then we should try to replace those dependencies with already existing entities. - void resolveDependencies(const std::unordered_map> & dependencies, - std::unordered_map & old_to_new_ids, - const AccessControl & access_control, - bool allow_unresolved_dependencies) - { - for (const auto & [id, name_and_type] : dependencies) - { - std::optional new_id; - if (allow_unresolved_dependencies) - new_id = access_control.find(name_and_type.second, name_and_type.first); - else - new_id = access_control.getID(name_and_type.second, name_and_type.first); - if (new_id) - old_to_new_ids.emplace(id, *new_id); - } - } - - /// Generates random IDs for the new entities. - void generateRandomIDs(std::vector> & entities, std::unordered_map & old_to_new_ids) - { - Poco::UUIDGenerator generator; - for (auto & [id, entity] : entities) - { - UUID new_id; - generator.createRandom().copyTo(reinterpret_cast(&new_id)); - old_to_new_ids.emplace(id, new_id); - id = new_id; - } - } - - /// Updates dependencies of the new entities using a specified map. - void replaceDependencies(std::vector> & entities, - const std::unordered_map & old_to_new_ids) - { - for (auto & entity : entities | boost::adaptors::map_values) - IAccessEntity::replaceDependencies(entity, old_to_new_ids); - } - - AccessRightsElements getRequiredAccessToRestore(const std::vector> & entities) - { - AccessRightsElements res; - for (const auto & entity : entities | boost::adaptors::map_values) - { - auto entity_type = entity->getType(); - switch (entity_type) - { - case User::TYPE: - { - const auto & user = typeid_cast(*entity); - res.emplace_back(AccessType::CREATE_USER); - auto elements = user.access.getElements(); - for (auto & element : elements) - { - if (element.is_partial_revoke) - continue; - element.grant_option = true; - res.emplace_back(element); - } - if (!user.granted_roles.isEmpty()) - res.emplace_back(AccessType::ROLE_ADMIN); - break; - } - - case Role::TYPE: - { - const auto & role = typeid_cast(*entity); - res.emplace_back(AccessType::CREATE_ROLE); - auto elements = role.access.getElements(); - for (auto & element : elements) - { - if (element.is_partial_revoke) - continue; - element.grant_option = true; - res.emplace_back(element); - } - if (!role.granted_roles.isEmpty()) - res.emplace_back(AccessType::ROLE_ADMIN); - break; - } - - case SettingsProfile::TYPE: - { - res.emplace_back(AccessType::CREATE_SETTINGS_PROFILE); - break; - } - - case RowPolicy::TYPE: - { - const auto & policy = typeid_cast(*entity); - res.emplace_back(AccessType::CREATE_ROW_POLICY, policy.getDatabase(), policy.getTableName()); - break; - } - - case Quota::TYPE: - { - res.emplace_back(AccessType::CREATE_QUOTA); - break; - } - - default: - throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown type: {}", toString(entity_type)); - } - } - return res; - } } -std::pair makeBackupEntryForAccess( - const std::vector> & access_entities, - const String & data_path_in_backup, - size_t counter, - const AccessControl & access_control) +std::pair makeBackupEntryForAccessEntities( + const std::vector & entities_ids, + const std::unordered_map & all_entities, + bool write_dependents, + const String & data_path_in_backup) { - auto dependencies = readDependenciesNamesAndTypes(findDependencies(access_entities), access_control); AccessEntitiesInBackup ab; - boost::range::copy(access_entities, std::inserter(ab.entities, ab.entities.end())); - ab.dependencies = std::move(dependencies); - String filename = fmt::format("access{:02}.txt", counter + 1); /// access01.txt, access02.txt, ... + + std::unordered_set entities_ids_set; + for (const auto & id : entities_ids) + entities_ids_set.emplace(id); + + for (const auto & id : entities_ids) + { + auto it = all_entities.find(id); + if (it != all_entities.end()) + { + AccessEntityPtr entity = it->second; + ab.entities.emplace(id, entity); + + auto dependencies = entity->findDependencies(); + for (const auto & dependency_id : dependencies) + { + if (!entities_ids_set.contains(dependency_id)) + { + auto it_dependency = all_entities.find(dependency_id); + if (it_dependency != all_entities.end()) + { + auto dependency_entity = it_dependency->second; + ab.dependencies.emplace(dependency_id, std::make_pair(dependency_entity->getName(), dependency_entity->getType())); + } + } + } + } + } + + if (write_dependents) + { + for (const auto & [id, possible_dependent] : all_entities) + { + if (!entities_ids_set.contains(id) && possible_dependent->hasDependencies(entities_ids_set)) + { + auto dependent = possible_dependent->clone(); + dependent->clearAllExceptDependencies(); + ab.dependents.emplace(id, dependent); + } + } + } + + String filename = fmt::format("access-{}.txt", UUIDHelpers::generateV4()); String file_path_in_backup = fs::path{data_path_in_backup} / filename; return {file_path_in_backup, ab.toBackupEntry()}; } @@ -339,61 +240,411 @@ AccessRestorerFromBackup::AccessRestorerFromBackup( const BackupPtr & backup_, const RestoreSettings & restore_settings_) : backup(backup_) , creation_mode(restore_settings_.create_access) - , allow_unresolved_dependencies(restore_settings_.allow_unresolved_access_dependencies) + , skip_unresolved_dependencies(restore_settings_.skip_unresolved_access_dependencies) + , update_dependents(restore_settings_.update_access_entities_dependents) + , log(getLogger("AccessRestorerFromBackup")) { } AccessRestorerFromBackup::~AccessRestorerFromBackup() = default; -void AccessRestorerFromBackup::addDataPath(const String & data_path) + +void AccessRestorerFromBackup::addDataPath(const String & data_path_in_backup) { - if (!data_paths.emplace(data_path).second) - return; + if (loaded) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Access entities already loaded"); - fs::path data_path_in_backup_fs = data_path; - Strings filenames = backup->listFiles(data_path, /*recursive*/ false); - if (filenames.empty()) - return; - - for (const String & filename : filenames) - { - if (!filename.starts_with("access") || !filename.ends_with(".txt")) - throw Exception(ErrorCodes::CANNOT_RESTORE_TABLE, "File name {} doesn't match the wildcard \"access*.txt\"", - String{data_path_in_backup_fs / filename}); - } - - ::sort(filenames.begin(), filenames.end()); - - for (const String & filename : filenames) - { - String filepath_in_backup = data_path_in_backup_fs / filename; - auto read_buffer_from_backup = backup->readFile(filepath_in_backup); - auto ab = AccessEntitiesInBackup::fromBackupEntry(std::move(read_buffer_from_backup), filepath_in_backup); - - boost::range::copy(ab.entities, std::back_inserter(entities)); - boost::range::copy(ab.dependencies, std::inserter(dependencies, dependencies.end())); - } - - for (const auto & id : entities | boost::adaptors::map_keys) - dependencies.erase(id); + if (std::find(data_paths_in_backup.begin(), data_paths_in_backup.end(), data_path_in_backup) == data_paths_in_backup.end()) + data_paths_in_backup.emplace_back(data_path_in_backup); } + +void AccessRestorerFromBackup::loadFromBackup() +{ + if (loaded) + return; + + /// Parse files "access*.txt" found in the added data paths in the backup. + for (size_t data_path_index = 0; data_path_index != data_paths_in_backup.size(); ++data_path_index) + { + const String & data_path_in_backup = data_paths_in_backup[data_path_index]; + + fs::path data_path_in_backup_fs = data_path_in_backup; + Strings filenames = backup->listFiles(data_path_in_backup_fs, /*recursive*/ false); + if (filenames.empty()) + continue; + + for (const String & filename : filenames) + { + if (!filename.starts_with("access") || !filename.ends_with(".txt")) + throw Exception(ErrorCodes::CANNOT_RESTORE_TABLE, "File name {} doesn't match the wildcard \"access*.txt\"", + String{data_path_in_backup_fs / filename}); + } + + for (const String & filename : filenames) + { + String filepath_in_backup = data_path_in_backup_fs / filename; + AccessEntitiesInBackup ab; + + try + { + auto read_buffer_from_backup = backup->readFile(filepath_in_backup); + ab = AccessEntitiesInBackup::fromBackupEntry(std::move(read_buffer_from_backup), filepath_in_backup); + } + catch (Exception & e) + { + e.addMessage("While reading access entities from {} in backup", filepath_in_backup); + throw; + } + + for (const auto & [id, entity] : ab.entities) + { + auto it = entity_infos.find(id); + if (it == entity_infos.end()) + { + it = entity_infos.emplace(id, EntityInfo{.id = id, .name = entity->getName(), .type = entity->getType()}).first; + } + EntityInfo & entity_info = it->second; + entity_info.entity = entity; + entity_info.restore = true; + entity_info.data_path_index = data_path_index; + } + + for (const auto & [id, name_and_type] : ab.dependencies) + { + auto it = entity_infos.find(id); + if (it == entity_infos.end()) + { + it = entity_infos.emplace(id, EntityInfo{.id = id, .name = name_and_type.first, .type = name_and_type.second}).first; + } + EntityInfo & entity_info = it->second; + entity_info.is_dependency = true; + } + + for (const auto & [id, entity] : ab.dependents) + { + auto it = entity_infos.find(id); + if (it == entity_infos.end()) + { + it = entity_infos.emplace(id, EntityInfo{.id = id, .name = entity->getName(), .type = entity->getType()}).first; + } + EntityInfo & entity_info = it->second; + if (!entity_info.restore) + entity_info.entity = entity; + } + } + } + + loaded = true; +} + + AccessRightsElements AccessRestorerFromBackup::getRequiredAccess() const { - return getRequiredAccessToRestore(entities); + if (!loaded) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Access entities not loaded"); + + AccessRightsElements res; + for (const auto & [id, entity_info] : entity_infos) + { + if (!entity_info.restore) + continue; + const auto & entity = entity_info.entity; + auto entity_type = entity->getType(); + switch (entity_type) + { + case User::TYPE: + { + const auto & user = typeid_cast(*entity); + res.emplace_back(AccessType::CREATE_USER); + auto elements = user.access.getElements(); + for (auto & element : elements) + { + if (element.is_partial_revoke) + continue; + element.grant_option = true; + res.emplace_back(element); + } + if (!user.granted_roles.isEmpty()) + res.emplace_back(AccessType::ROLE_ADMIN); + break; + } + + case Role::TYPE: + { + const auto & role = typeid_cast(*entity); + res.emplace_back(AccessType::CREATE_ROLE); + auto elements = role.access.getElements(); + for (auto & element : elements) + { + if (element.is_partial_revoke) + continue; + element.grant_option = true; + res.emplace_back(element); + } + if (!role.granted_roles.isEmpty()) + res.emplace_back(AccessType::ROLE_ADMIN); + break; + } + + case SettingsProfile::TYPE: + { + res.emplace_back(AccessType::CREATE_SETTINGS_PROFILE); + break; + } + + case RowPolicy::TYPE: + { + const auto & policy = typeid_cast(*entity); + res.emplace_back(AccessType::CREATE_ROW_POLICY, policy.getDatabase(), policy.getTableName()); + break; + } + + case Quota::TYPE: + { + res.emplace_back(AccessType::CREATE_QUOTA); + break; + } + + default: + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown type: {}", toString(entity_type)); + } + } + return res; } -std::vector> AccessRestorerFromBackup::getAccessEntities(const AccessControl & access_control) const + +void AccessRestorerFromBackup::generateRandomIDsAndResolveDependencies(const AccessControl & access_control) { - auto new_entities = entities; + if (ids_assigned) + return; + if (!loaded) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Access entities not loaded"); + + /// Calculate `new_id` for each entity info. + /// Check which ones of the loaded access entities already exist. + /// Generate random UUIDs for access entities which we're going to restore if they don't exist. + for (auto & [id, entity_info] : entity_infos) + { + const String & name = entity_info.name; + auto type = entity_info.type; + + if (entity_info.restore && (creation_mode == RestoreAccessCreationMode::kReplace)) + { + entity_info.new_id = UUIDHelpers::generateV4(); + LOG_TRACE(log, "{}: Generated new UUID {}", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name), *entity_info.new_id); + continue; + } + + if (auto existing_id = access_control.find(type, name)) + { + if (entity_info.restore && (creation_mode == RestoreAccessCreationMode::kCreate)) + { + throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Cannot restore {} because it already exists", + AccessEntityTypeInfo::get(type).formatEntityNameWithType(name)); + } + bool was_going_to_restore = entity_info.restore; + entity_info.new_id = *existing_id; + entity_info.restore = false; + LOG_TRACE(log, "{}: Found with UUID {}{}", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name), *existing_id, + (was_going_to_restore ? ", will not restore" : "")); + } + else + { + if (entity_info.restore) + { + entity_info.new_id = UUIDHelpers::generateV4(); + LOG_TRACE(log, "{}: Generated new UUID {}", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name), *entity_info.new_id); + } + else if (skip_unresolved_dependencies) + { + LOG_TRACE(log, "{}: Not found, ignoring", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name)); + } + else + { + throw Exception(ErrorCodes::ACCESS_ENTITY_NOT_FOUND, "Cannot resolve {} while restoring from backup", + AccessEntityTypeInfo::get(type).formatEntityNameWithType(name)); + } + } + } + + /// Prepare map from old UUIDs to new UUIDs. + std::unordered_set ids_to_restore; std::unordered_map old_to_new_ids; - checkExistingEntities(new_entities, old_to_new_ids, access_control, creation_mode); - resolveDependencies(dependencies, old_to_new_ids, access_control, allow_unresolved_dependencies); - generateRandomIDs(new_entities, old_to_new_ids); - replaceDependencies(new_entities, old_to_new_ids); + std::unordered_set unresolved_ids; - return new_entities; + for (const auto & [id, entity_info] : entity_infos) + { + if (entity_info.restore) + ids_to_restore.insert(id); + + if (entity_info.new_id) + old_to_new_ids[id] = *entity_info.new_id; + else + unresolved_ids.insert(id); + } + + /// Calculate `is_dependent` for each entity info. + if (update_dependents) + { + for (auto & [id, entity_info] : entity_infos) + { + if (!entity_info.restore && entity_info.new_id && entity_info.entity && entity_info.entity->hasDependencies(ids_to_restore)) + entity_info.is_dependent = true; + } + } + + /// Remap the UUIDs of dependencies in the access entities we're going to restore. + for (auto & [id, entity_info] : entity_infos) + { + if (entity_info.restore || entity_info.is_dependent) + { + auto new_entity = entity_info.entity->clone(); + new_entity->replaceDependencies(old_to_new_ids); + new_entity->removeDependencies(unresolved_ids); + entity_info.entity = new_entity; + } + + if (entity_info.restore && data_path_with_entities_to_restore.empty()) + data_path_with_entities_to_restore = data_paths_in_backup[entity_info.data_path_index]; + } + + ids_assigned = true; +} + + +AccessEntitiesToRestore AccessRestorerFromBackup::getEntitiesToRestore(const String & data_path_in_backup) const +{ + if (!ids_assigned) + throw Exception(ErrorCodes::LOGICAL_ERROR, "IDs not assigned"); + + if (data_path_in_backup != data_path_with_entities_to_restore) + return {}; + + AccessEntitiesToRestore res; + res.new_entities.reserve(entity_infos.size()); + res.dependents.reserve(entity_infos.size()); + + for (const auto & [id, entity_info] : entity_infos) + { + if (entity_info.restore) + res.new_entities.emplace_back(*entity_info.new_id, entity_info.entity); + + if (entity_info.is_dependent) + res.dependents.emplace_back(AccessEntitiesToRestore::Dependent{*entity_info.new_id, entity_info.entity}); + } + + return res; +} + + +void restoreAccessEntitiesFromBackup( + IAccessStorage & destination_access_storage, + const AccessEntitiesToRestore & entities_to_restore, + const RestoreSettings & restore_settings) +{ + if (entities_to_restore.new_entities.empty()) + return; /// Nothing to restore. + + auto log = getLogger("AccessRestorerFromBackup"); + + bool replace_if_exists = (restore_settings.create_access == RestoreAccessCreationMode::kReplace); + bool throw_if_exists = (restore_settings.create_access == RestoreAccessCreationMode::kCreate); + bool update_dependents = restore_settings.update_access_entities_dependents; + + std::unordered_set restored_ids; + std::unordered_map new_to_existing_ids; + AccessEntitiesToRestore::Dependents additional_dependents; + additional_dependents.reserve(entities_to_restore.new_entities.size()); + + for (const auto & [id, entity] : entities_to_restore.new_entities) + { + const String & name = entity->getName(); + auto type = entity->getType(); + LOG_TRACE(log, "{}: Adding with UUID {}", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name), id); + + UUID existing_id; + if (destination_access_storage.insert(id, entity, replace_if_exists, throw_if_exists, &existing_id)) + { + LOG_TRACE(log, "{}: Added successfully", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name)); + restored_ids.emplace(id); + } + else + { + /// Couldn't insert `entity` because there is an existing entity with the same name. + LOG_TRACE(log, "{}: Not added because already exists with UUID {}", AccessEntityTypeInfo::get(type).formatEntityNameWithType(name), existing_id); + new_to_existing_ids[id] = existing_id; + if (update_dependents) + additional_dependents.emplace_back(AccessEntitiesToRestore::Dependent{existing_id, entity}); + } + } + + if (!new_to_existing_ids.empty()) + { + std::vector ids_to_update; + ids_to_update.reserve(restored_ids.size()); + boost::copy(restored_ids, std::inserter(ids_to_update, ids_to_update.end())); + + std::unordered_set new_ids; + boost::copy(new_to_existing_ids | boost::adaptors::map_keys, std::inserter(new_ids, new_ids.end())); + + /// If new entities restored from backup have dependencies on other entities from backup which were not restored because they existed, + /// then we should correct those dependencies. + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr + { + if (!entity->hasDependencies(new_ids)) + return entity; + LOG_TRACE(log, "{}: Updating dependencies", entity->formatTypeWithName()); + auto res = entity->clone(); + res->replaceDependencies(new_to_existing_ids); + return res; + }; + + /// It's totally ok if some UUIDs from `ids_to_update` don't exist anymore, that's why we use tryUpdate() here. + destination_access_storage.tryUpdate(ids_to_update, update_func); + } + + auto do_update_dependents = [&](const AccessEntitiesToRestore::Dependents & dependents) + { + if (dependents.empty()) + return; + + std::vector ids_to_update; + ids_to_update.reserve(dependents.size()); + std::unordered_map id_to_source; + + for (const auto & dependent : dependents) + { + const UUID & id = dependent.existing_id; + if (!destination_access_storage.isReadOnly(id)) + { + ids_to_update.emplace_back(id); + auto modified_source = dependent.source->clone(); + modified_source->replaceDependencies(new_to_existing_ids); + id_to_source[id] = modified_source; + } + } + + /// If new entities restored from backup have dependencies on other entities from backup which were not restored because they existed, + /// then we should correct those dependencies. + auto update_func = [&](const AccessEntityPtr & entity, const UUID & existing_id) -> AccessEntityPtr + { + const auto & source = *id_to_source.at(existing_id); + if (!source.hasDependencies(restored_ids)) + return entity; + LOG_TRACE(log, "{}: Updating dependent", entity->formatTypeWithName()); + auto res = entity->clone(); + res->copyDependenciesFrom(source, restored_ids); + return res; + }; + + /// It's totally ok if some UUIDs from `ids_to_update` don't exist anymore, that's why we use tryUpdate() here. + destination_access_storage.tryUpdate(ids_to_update, update_func); + }; + + do_update_dependents(entities_to_restore.dependents); + do_update_dependents(additional_dependents); } } diff --git a/src/Access/AccessBackup.h b/src/Access/AccessBackup.h index 51a1112e5d5..d5133b3cb9f 100644 --- a/src/Access/AccessBackup.h +++ b/src/Access/AccessBackup.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include namespace DB @@ -12,6 +12,7 @@ enum class AccessEntityType : uint8_t; struct IAccessEntity; using AccessEntityPtr = std::shared_ptr; class AccessRightsElements; +class IAccessStorage; class IBackup; using BackupPtr = std::shared_ptr; class IBackupEntry; @@ -20,15 +21,42 @@ struct RestoreSettings; enum class RestoreAccessCreationMode : uint8_t; -/// Makes a backup of access entities of a specified type. -std::pair makeBackupEntryForAccess( - const std::vector> & access_entities, - const String & data_path_in_backup, - size_t counter, - const AccessControl & access_control); +/// Makes a backup entry for of a set of access entities. +std::pair makeBackupEntryForAccessEntities( + const std::vector & entities_ids, + const std::unordered_map & all_entities, + bool write_dependents, + const String & data_path_in_backup); +struct AccessEntitiesToRestore +{ + /// Access entities loaded from backup with new randomly generated UUIDs. + std::vector> new_entities; + + /// Dependents are access entities which exist already and they should be updated after restoring. + /// For example, if there were a role granted to a user: `CREATE USER user1; CREATE ROLE role1; GRANT role1 TO user1`, + /// and we're restoring only role `role1` because user `user1` already exists, + /// then user `user1` should be modified after restoring role `role1` to add this grant `GRANT role1 TO user1`. + struct Dependent + { + /// UUID of an existing access entities. + UUID existing_id; + + /// Source access entity from backup to copy dependencies from. + AccessEntityPtr source; + }; + using Dependents = std::vector; + Dependents dependents; +}; /// Restores access entities from a backup. +void restoreAccessEntitiesFromBackup( + IAccessStorage & access_storage, + const AccessEntitiesToRestore & entities_to_restore, + const RestoreSettings & restore_settings); + + +/// Loads access entities from a backup and prepares them for insertion into an access storage. class AccessRestorerFromBackup { public: @@ -36,21 +64,75 @@ public: ~AccessRestorerFromBackup(); /// Adds a data path to loads access entities from. - void addDataPath(const String & data_path); + void addDataPath(const String & data_path_in_backup); + + /// Loads access entities from the backup. + void loadFromBackup(); /// Checks that the current user can do restoring. + /// Function loadFromBackup() must be called before that. AccessRightsElements getRequiredAccess() const; - /// Inserts all access entities loaded from all the paths added by addDataPath(). - std::vector> getAccessEntities(const AccessControl & access_control) const; + /// Generates random IDs for access entities we're restoring to insert them into an access storage; + /// and finds IDs of existing access entities which are used as dependencies. + void generateRandomIDsAndResolveDependencies(const AccessControl & access_control); + + /// Returns access entities loaded from backup and prepared for insertion into an access storage. + /// Both functions loadFromBackup() and generateRandomIDsAndResolveDependencies() must be called before that. + AccessEntitiesToRestore getEntitiesToRestore(const String & data_path_in_backup) const; private: - BackupPtr backup; - RestoreAccessCreationMode creation_mode; - bool allow_unresolved_dependencies = false; - std::vector> entities; - std::unordered_map> dependencies; - std::unordered_set data_paths; + const BackupPtr backup; + const RestoreAccessCreationMode creation_mode; + const bool skip_unresolved_dependencies; + const bool update_dependents; + const LoggerPtr log; + + /// Whether loadFromBackup() finished. + bool loaded = false; + + /// Whether generateRandomIDsAndResolveDependencies() finished. + bool ids_assigned = false; + + Strings data_paths_in_backup; + String data_path_with_entities_to_restore; + + /// Information about an access entity loaded from the backup. + struct EntityInfo + { + UUID id; + String name; + AccessEntityType type; + + AccessEntityPtr entity = nullptr; /// Can be nullptr if `restore=false`. + + /// Index in `data_paths_in_backup`. + size_t data_path_index = 0; + + /// Whether we're going to restore this entity. + /// For example, + /// in case of `RESTORE TABLE system.roles` this flag is true for all the roles loaded from the backup, and + /// in case of `RESTORE ALL` this flag is always true. + bool restore = false; + + /// Whether this entity info was added as a dependency of another entity which we're going to restore. + /// For example, if we're going to restore the following user: `CREATE USER user1 DEFAULT ROLE role1, role2 SETTINGS PROFILE profile1, profile2` + /// then `restore=true` for `user1` and `is_dependency=true` for `role1`, `role2`, `profile1`, `profile2`. + /// Flags `restore` and `is_dependency` both can be set at the same time. + bool is_dependency = false; + + /// Whether this entity info is a dependent of another entity which we're going to restore. + /// For example, if we're going to restore role `role1` and there is also the following user stored in the backup: + /// `CREATE USER user1 DEFAULT ROLE role1`, then `is_dependent=true` for `user1`. + /// This flags is set by generateRandomIDsAndResolveDependencies(). + bool is_dependent = false; + + /// New UUID for this entity - either randomly generated or copied from an existing entity. + /// This UUID is assigned by generateRandomIDsAndResolveDependencies(). + std::optional new_id = std::nullopt; + }; + + std::unordered_map entity_infos; }; } diff --git a/src/Access/AccessControl.cpp b/src/Access/AccessControl.cpp index ec513f0692d..8e656372637 100644 --- a/src/Access/AccessControl.cpp +++ b/src/Access/AccessControl.cpp @@ -629,9 +629,9 @@ AuthResult AccessControl::authenticate(const Credentials & credentials, const Po } } -void AccessControl::restoreFromBackup(RestorerFromBackup & restorer) +void AccessControl::restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup) { - MultipleAccessStorage::restoreFromBackup(restorer); + MultipleAccessStorage::restoreFromBackup(restorer, data_path_in_backup); changes_notifier->sendNotifications(); } diff --git a/src/Access/AccessControl.h b/src/Access/AccessControl.h index 0c3bb9352f0..cc1b7b2ca0d 100644 --- a/src/Access/AccessControl.h +++ b/src/Access/AccessControl.h @@ -124,7 +124,7 @@ public: AuthResult authenticate(const Credentials & credentials, const Poco::Net::IPAddress & address, const String & forwarded_address) const; /// Makes a backup of access entities. - void restoreFromBackup(RestorerFromBackup & restorer) override; + void restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup) override; void setExternalAuthenticatorsConfig(const Poco::Util::AbstractConfiguration & config); diff --git a/src/Access/DiskAccessStorage.cpp b/src/Access/DiskAccessStorage.cpp index 046c532cf5c..a9c69661725 100644 --- a/src/Access/DiskAccessStorage.cpp +++ b/src/Access/DiskAccessStorage.cpp @@ -676,7 +676,7 @@ bool DiskAccessStorage::updateNoLock(const UUID & id, const UpdateFunc & update_ if (!entry.entity) entry.entity = readAccessEntityFromDisk(id); auto old_entity = entry.entity; - auto new_entity = update_func(old_entity); + auto new_entity = update_func(old_entity, id); if (!new_entity->isTypeOf(old_entity->getType())) throwBadCast(id, new_entity->getType(), new_entity->getName(), old_entity->getType()); diff --git a/src/Access/GrantedRoles.cpp b/src/Access/GrantedRoles.cpp index e1a23182cc0..76e77599492 100644 --- a/src/Access/GrantedRoles.cpp +++ b/src/Access/GrantedRoles.cpp @@ -176,6 +176,16 @@ std::vector GrantedRoles::findDependencies() const return res; } +bool GrantedRoles::hasDependencies(const std::unordered_set & ids) const +{ + for (const auto & role_id : roles) + { + if (ids.contains(role_id)) + return true; + } + return false; +} + void GrantedRoles::replaceDependencies(const std::unordered_map & old_to_new_ids) { std::vector new_ids; @@ -221,4 +231,56 @@ void GrantedRoles::replaceDependencies(const std::unordered_map & ol } } +void GrantedRoles::copyDependenciesFrom(const GrantedRoles & src, const std::unordered_set & ids) +{ + bool found = false; + + for (const auto & role_id : src.roles) + { + if (ids.contains(role_id)) + { + roles.emplace(role_id); + found = true; + } + } + + if (found) + { + for (const auto & role_id : src.roles_with_admin_option) + { + if (ids.contains(role_id)) + roles_with_admin_option.emplace(role_id); + } + } +} + +void GrantedRoles::removeDependencies(const std::unordered_set & ids) +{ + bool found = false; + + for (auto it = roles.begin(); it != roles.end();) + { + if (ids.contains(*it)) + { + it = roles.erase(it); + found = true; + } + else + { + ++it; + } + } + + if (found) + { + for (auto it = roles_with_admin_option.begin(); it != roles_with_admin_option.end();) + { + if (ids.contains(*it)) + it = roles_with_admin_option.erase(it); + else + ++it; + } + } +} + } diff --git a/src/Access/GrantedRoles.h b/src/Access/GrantedRoles.h index ac252822089..d92af0f6db1 100644 --- a/src/Access/GrantedRoles.h +++ b/src/Access/GrantedRoles.h @@ -58,7 +58,10 @@ public: friend bool operator !=(const GrantedRoles & left, const GrantedRoles & right) { return !(left == right); } std::vector findDependencies() const; + bool hasDependencies(const std::unordered_set & ids) const; void replaceDependencies(const std::unordered_map & old_to_new_ids); + void copyDependenciesFrom(const GrantedRoles & src, const std::unordered_set & ids); + void removeDependencies(const std::unordered_set & ids); private: boost::container::flat_set roles; diff --git a/src/Access/IAccessEntity.cpp b/src/Access/IAccessEntity.cpp index 9afa1b73597..5dc566fe456 100644 --- a/src/Access/IAccessEntity.cpp +++ b/src/Access/IAccessEntity.cpp @@ -9,28 +9,4 @@ bool IAccessEntity::equal(const IAccessEntity & other) const return (name == other.name) && (getType() == other.getType()); } -void IAccessEntity::replaceDependencies(std::shared_ptr & entity, const std::unordered_map & old_to_new_ids) -{ - if (old_to_new_ids.empty()) - return; - - bool need_replace_dependencies = false; - auto dependencies = entity->findDependencies(); - for (const auto & dependency : dependencies) - { - if (old_to_new_ids.contains(dependency)) - { - need_replace_dependencies = true; - break; - } - } - - if (!need_replace_dependencies) - return; - - auto new_entity = entity->clone(); - new_entity->replaceDependencies(old_to_new_ids); - entity = new_entity; -} - } diff --git a/src/Access/IAccessEntity.h b/src/Access/IAccessEntity.h index 2c2df7353c5..09c5eb6cf10 100644 --- a/src/Access/IAccessEntity.h +++ b/src/Access/IAccessEntity.h @@ -48,10 +48,13 @@ struct IAccessEntity /// Finds all dependencies. virtual std::vector findDependencies() const { return {}; } + virtual bool hasDependencies(const std::unordered_set & /* ids */) const { return false; } /// Replaces dependencies according to a specified map. - void replaceDependencies(const std::unordered_map & old_to_new_ids) { doReplaceDependencies(old_to_new_ids); } - static void replaceDependencies(std::shared_ptr & entity, const std::unordered_map & old_to_new_ids); + virtual void replaceDependencies(const std::unordered_map & /* old_to_new_ids */) {} + virtual void copyDependenciesFrom(const IAccessEntity & /* src */, const std::unordered_set & /* ids */) {} + virtual void removeDependencies(const std::unordered_set & /* ids */) {} + virtual void clearAllExceptDependencies() {} /// Whether this access entity should be written to a backup. virtual bool isBackupAllowed() const { return false; } @@ -67,8 +70,6 @@ protected: { return std::make_shared(typeid_cast(*this)); } - - virtual void doReplaceDependencies(const std::unordered_map & /* old_to_new_ids */) {} }; using AccessEntityPtr = std::shared_ptr; diff --git a/src/Access/IAccessStorage.cpp b/src/Access/IAccessStorage.cpp index 29475461c45..3249d89ba87 100644 --- a/src/Access/IAccessStorage.cpp +++ b/src/Access/IAccessStorage.cpp @@ -4,8 +4,10 @@ #include #include #include -#include +#include +#include #include +#include #include #include #include @@ -14,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +74,18 @@ std::vector IAccessStorage::find(AccessEntityType type, const Strings & na } +std::vector IAccessStorage::findAllImpl() const +{ + std::vector res; + for (auto type : collections::range(AccessEntityType::MAX)) + { + auto ids = findAllImpl(type); + res.insert(res.end(), ids.begin(), ids.end()); + } + return res; +} + + UUID IAccessStorage::getID(AccessEntityType type, const String & name) const { auto id = findImpl(type, name); @@ -598,67 +613,59 @@ void IAccessStorage::backup(BackupEntriesCollector & backup_entries_collector, c if (!isBackupAllowed()) throwBackupNotAllowed(); - auto entities = readAllWithIDs(type); - std::erase_if(entities, [](const std::pair & x) { return !x.second->isBackupAllowed(); }); - - if (entities.empty()) + auto entities_ids = findAll(type); + if (entities_ids.empty()) return; - auto backup_entry = makeBackupEntryForAccess( - entities, - data_path_in_backup, - backup_entries_collector.getAccessCounter(type), - backup_entries_collector.getContext()->getAccessControl()); + auto backup_entry_with_path = makeBackupEntryForAccessEntities( + entities_ids, + backup_entries_collector.getAllAccessEntities(), + backup_entries_collector.getBackupSettings().write_access_entities_dependents, + data_path_in_backup); - backup_entries_collector.addBackupEntry(backup_entry); + if (isReplicated()) + { + auto backup_coordination = backup_entries_collector.getBackupCoordination(); + auto replication_id = getReplicationID(); + backup_coordination->addReplicatedAccessFilePath(replication_id, type, backup_entry_with_path.first); + + backup_entries_collector.addPostTask( + [backup_entry = backup_entry_with_path.second, + replication_id, + type, + &backup_entries_collector, + backup_coordination] + { + for (const String & path : backup_coordination->getReplicatedAccessFilePaths(replication_id, type)) + backup_entries_collector.addBackupEntry(path, backup_entry); + }); + } + else + { + backup_entries_collector.addBackupEntry(backup_entry_with_path); + } } -void IAccessStorage::restoreFromBackup(RestorerFromBackup & restorer) +void IAccessStorage::restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup) { if (!isRestoreAllowed()) throwRestoreNotAllowed(); - if (isReplicated() && !acquireReplicatedRestore(restorer)) - return; - - auto entities = restorer.getAccessEntitiesToRestore(); - if (entities.empty()) - return; - - auto create_access = restorer.getRestoreSettings().create_access; - bool replace_if_exists = (create_access == RestoreAccessCreationMode::kReplace); - bool throw_if_exists = (create_access == RestoreAccessCreationMode::kCreate); - - restorer.addDataRestoreTask([this, entities_to_restore = std::move(entities), replace_if_exists, throw_if_exists] mutable + if (isReplicated()) { - std::unordered_map new_to_existing_ids; - for (auto & [id, entity] : entities_to_restore) - { - UUID existing_entity_id; - if (!insert(id, entity, replace_if_exists, throw_if_exists, &existing_entity_id)) - { - /// Couldn't insert `entity` because there is an existing entity with the same name. - new_to_existing_ids[id] = existing_entity_id; - } - } + auto restore_coordination = restorer.getRestoreCoordination(); + if (!restore_coordination->acquireReplicatedAccessStorage(getReplicationID())) + return; + } - if (!new_to_existing_ids.empty()) + restorer.addDataRestoreTask( + [this, &restorer, data_path_in_backup] { - /// If new entities restored from backup have dependencies on other entities from backup which were not restored because they existed, - /// then we should correct those dependencies. - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr - { - auto res = entity; - IAccessEntity::replaceDependencies(res, new_to_existing_ids); - return res; - }; - std::vector ids; - ids.reserve(entities_to_restore.size()); - boost::copy(entities_to_restore | boost::adaptors::map_keys, std::back_inserter(ids)); - tryUpdate(ids, update_func); - } - }); + auto entities_to_restore = restorer.getAccessEntitiesToRestore(data_path_in_backup); + const auto & restore_settings = restorer.getRestoreSettings(); + restoreAccessEntitiesFromBackup(*this, entities_to_restore, restore_settings); + }); } diff --git a/src/Access/IAccessStorage.h b/src/Access/IAccessStorage.h index a8ac75075d3..84cbdd0a751 100644 --- a/src/Access/IAccessStorage.h +++ b/src/Access/IAccessStorage.h @@ -66,6 +66,7 @@ public: /// Returns true if this storage is replicated. virtual bool isReplicated() const { return false; } + virtual String getReplicationID() const { return ""; } /// Starts periodic reloading and updating of entities in this storage. virtual void startPeriodicReloading() {} @@ -90,8 +91,9 @@ public: /// Returns the identifiers of all the entities of a specified type contained in the storage. std::vector findAll(AccessEntityType type) const; - template - std::vector findAll() const { return findAll(EntityClassT::TYPE); } + /// Returns the identifiers of all the entities in the storage. + template + std::vector findAll() const; /// Searches for an entity with specified type and name. Returns std::nullopt if not found. std::optional find(AccessEntityType type, const String & name) const; @@ -148,7 +150,7 @@ public: std::optional> tryReadNameWithType(const UUID & id) const; /// Reads all entities and returns them with their IDs. - template + template std::vector>> readAllWithIDs() const; std::vector> readAllWithIDs(AccessEntityType type) const; @@ -180,7 +182,7 @@ public: /// Removes multiple entities from the storage. Returns the list of successfully dropped. std::vector tryRemove(const std::vector & ids); - using UpdateFunc = std::function; + using UpdateFunc = std::function; /// Updates an entity stored in the storage. Throws an exception if couldn't update. bool update(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists = true); @@ -214,11 +216,12 @@ public: /// Makes a backup of this access storage. virtual void backup(BackupEntriesCollector & backup_entries_collector, const String & data_path_in_backup, AccessEntityType type) const; - virtual void restoreFromBackup(RestorerFromBackup & restorer); + virtual void restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup); protected: virtual std::optional findImpl(AccessEntityType type, const String & name) const = 0; virtual std::vector findAllImpl(AccessEntityType type) const = 0; + virtual std::vector findAllImpl() const; virtual AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const = 0; virtual std::optional> readNameWithTypeImpl(const UUID & id, bool throw_if_not_exists) const; virtual bool insertImpl(const UUID & id, const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, UUID * conflicting_id); @@ -267,6 +270,16 @@ private: }; +template +std::vector IAccessStorage::findAll() const +{ + if constexpr (std::is_same_v) + return findAllImpl(); + else + return findAllImpl(EntityClassT::TYPE); +} + + template std::shared_ptr IAccessStorage::read(const UUID & id, bool throw_if_not_exists) const { diff --git a/src/Access/LDAPAccessStorage.cpp b/src/Access/LDAPAccessStorage.cpp index 3576d38fbda..4fbe0242bd7 100644 --- a/src/Access/LDAPAccessStorage.cpp +++ b/src/Access/LDAPAccessStorage.cpp @@ -163,7 +163,7 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id, // Update the granted roles of the relevant users. if (!user_ids.empty()) { - auto update_func = [&role_id, &grant] (const AccessEntityPtr & entity_) -> AccessEntityPtr + auto update_func = [&role_id, &grant] (const AccessEntityPtr & entity_, const UUID &) -> AccessEntityPtr { if (auto user = typeid_cast>(entity_)) { @@ -301,7 +301,7 @@ void LDAPAccessStorage::updateAssignedRolesNoLock(const UUID & id, const String if (it != external_role_hashes.end() && it->second == external_roles_hash) return; - auto update_func = [this, &external_roles, external_roles_hash] (const AccessEntityPtr & entity_) -> AccessEntityPtr + auto update_func = [this, &external_roles, external_roles_hash] (const AccessEntityPtr & entity_, const UUID &) -> AccessEntityPtr { if (auto user = typeid_cast>(entity_)) { diff --git a/src/Access/MemoryAccessStorage.cpp b/src/Access/MemoryAccessStorage.cpp index 3b5a987fc6e..e1743cdd6a4 100644 --- a/src/Access/MemoryAccessStorage.cpp +++ b/src/Access/MemoryAccessStorage.cpp @@ -204,7 +204,7 @@ bool MemoryAccessStorage::updateNoLock(const UUID & id, const UpdateFunc & updat Entry & entry = it->second; auto old_entity = entry.entity; - auto new_entity = update_func(old_entity); + auto new_entity = update_func(old_entity, id); if (!new_entity->isTypeOf(old_entity->getType())) throwBadCast(id, new_entity->getType(), new_entity->getName(), old_entity->getType()); diff --git a/src/Access/MultipleAccessStorage.cpp b/src/Access/MultipleAccessStorage.cpp index f1da8359d48..d51a7e00511 100644 --- a/src/Access/MultipleAccessStorage.cpp +++ b/src/Access/MultipleAccessStorage.cpp @@ -416,7 +416,7 @@ bool MultipleAccessStorage::updateImpl(const UUID & id, const UpdateFunc & updat { if (auto old_entity = storage_for_updating->tryRead(id)) { - auto new_entity = update_func(old_entity); + auto new_entity = update_func(old_entity, id); if (new_entity->getName() != old_entity->getName()) { for (const auto & storage : *storages) @@ -508,7 +508,7 @@ void MultipleAccessStorage::backup(BackupEntriesCollector & backup_entries_colle throwBackupNotAllowed(); } -void MultipleAccessStorage::restoreFromBackup(RestorerFromBackup & restorer) +void MultipleAccessStorage::restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup) { auto storages = getStoragesInternal(); @@ -516,7 +516,7 @@ void MultipleAccessStorage::restoreFromBackup(RestorerFromBackup & restorer) { if (storage->isRestoreAllowed()) { - storage->restoreFromBackup(restorer); + storage->restoreFromBackup(restorer, data_path_in_backup); return; } } diff --git a/src/Access/MultipleAccessStorage.h b/src/Access/MultipleAccessStorage.h index 352cc7f7457..06147a45ba7 100644 --- a/src/Access/MultipleAccessStorage.h +++ b/src/Access/MultipleAccessStorage.h @@ -59,7 +59,7 @@ public: bool isBackupAllowed() const override; bool isRestoreAllowed() const override; void backup(BackupEntriesCollector & backup_entries_collector, const String & data_path_in_backup, AccessEntityType type) const override; - void restoreFromBackup(RestorerFromBackup & restorer) override; + void restoreFromBackup(RestorerFromBackup & restorer, const String & data_path_in_backup) override; bool containsStorage(std::string_view storage_type) const; protected: diff --git a/src/Access/Quota.cpp b/src/Access/Quota.cpp index ead5f77ce57..3d4bff1ccba 100644 --- a/src/Access/Quota.cpp +++ b/src/Access/Quota.cpp @@ -24,9 +24,33 @@ std::vector Quota::findDependencies() const return to_roles.findDependencies(); } -void Quota::doReplaceDependencies(const std::unordered_map & old_to_new_ids) +bool Quota::hasDependencies(const std::unordered_set & ids) const +{ + return to_roles.hasDependencies(ids); +} + +void Quota::replaceDependencies(const std::unordered_map & old_to_new_ids) { to_roles.replaceDependencies(old_to_new_ids); } +void Quota::copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) +{ + if (getType() != src.getType()) + return; + const auto & src_quota = typeid_cast(src); + to_roles.copyDependenciesFrom(src_quota.to_roles, ids); +} + +void Quota::removeDependencies(const std::unordered_set & ids) +{ + to_roles.removeDependencies(ids); +} + +void Quota::clearAllExceptDependencies() +{ + all_limits.clear(); + key_type = QuotaKeyType::NONE; +} + } diff --git a/src/Access/Quota.h b/src/Access/Quota.h index 69ec2eb53a5..2281714eb13 100644 --- a/src/Access/Quota.h +++ b/src/Access/Quota.h @@ -47,7 +47,12 @@ struct Quota : public IAccessEntity AccessEntityType getType() const override { return TYPE; } std::vector findDependencies() const override; - void doReplaceDependencies(const std::unordered_map & old_to_new_ids) override; + bool hasDependencies(const std::unordered_set & ids) const override; + void replaceDependencies(const std::unordered_map & old_to_new_ids) override; + void copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) override; + void removeDependencies(const std::unordered_set & ids) override; + void clearAllExceptDependencies() override; + bool isBackupAllowed() const override { return true; } }; diff --git a/src/Access/ReplicatedAccessStorage.cpp b/src/Access/ReplicatedAccessStorage.cpp index 9039a3b98b7..f0cb8a5c9f0 100644 --- a/src/Access/ReplicatedAccessStorage.cpp +++ b/src/Access/ReplicatedAccessStorage.cpp @@ -4,10 +4,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -359,7 +355,7 @@ bool ReplicatedAccessStorage::updateZooKeeper(const zkutil::ZooKeeperPtr & zooke } const AccessEntityPtr old_entity = deserializeAccessEntity(old_entity_definition, entity_path); - const AccessEntityPtr new_entity = update_func(old_entity); + const AccessEntityPtr new_entity = update_func(old_entity, id); if (!new_entity->isTypeOf(old_entity->getType())) throwBadCast(id, new_entity->getType(), new_entity->getName(), old_entity->getType()); @@ -684,44 +680,4 @@ AccessEntityPtr ReplicatedAccessStorage::readImpl(const UUID & id, bool throw_if return memory_storage.read(id, throw_if_not_exists); } - -void ReplicatedAccessStorage::backup(BackupEntriesCollector & backup_entries_collector, const String & data_path_in_backup, AccessEntityType type) const -{ - if (!isBackupAllowed()) - throwBackupNotAllowed(); - - auto entities = readAllWithIDs(type); - std::erase_if(entities, [](const std::pair & x) { return !x.second->isBackupAllowed(); }); - - if (entities.empty()) - return; - - auto backup_entry_with_path = makeBackupEntryForAccess( - entities, - data_path_in_backup, - backup_entries_collector.getAccessCounter(type), - backup_entries_collector.getContext()->getAccessControl()); - - auto backup_coordination = backup_entries_collector.getBackupCoordination(); - backup_coordination->addReplicatedAccessFilePath(zookeeper_path, type, backup_entry_with_path.first); - - backup_entries_collector.addPostTask( - [backup_entry = backup_entry_with_path.second, - my_zookeeper_path = zookeeper_path, - type, - &backup_entries_collector, - backup_coordination] - { - for (const String & path : backup_coordination->getReplicatedAccessFilePaths(my_zookeeper_path, type)) - backup_entries_collector.addBackupEntry(path, backup_entry); - }); -} - - -bool ReplicatedAccessStorage::acquireReplicatedRestore(RestorerFromBackup & restorer) const -{ - auto restore_coordination = restorer.getRestoreCoordination(); - return restore_coordination->acquireReplicatedAccessStorage(zookeeper_path); -} - } diff --git a/src/Access/ReplicatedAccessStorage.h b/src/Access/ReplicatedAccessStorage.h index 528dbb31c24..e46e854d414 100644 --- a/src/Access/ReplicatedAccessStorage.h +++ b/src/Access/ReplicatedAccessStorage.h @@ -26,7 +26,9 @@ public: void shutdown() override; const char * getStorageType() const override { return STORAGE_TYPE; } + bool isReplicated() const override { return true; } + String getReplicationID() const override { return zookeeper_path; } void startPeriodicReloading() override { startWatchingThread(); } void stopPeriodicReloading() override { stopWatchingThread(); } @@ -35,7 +37,6 @@ public: bool exists(const UUID & id) const override; bool isBackupAllowed() const override { return backup_allowed; } - void backup(BackupEntriesCollector & backup_entries_collector, const String & data_path_in_backup, AccessEntityType type) const override; private: String zookeeper_path; @@ -80,7 +81,6 @@ private: std::optional findImpl(AccessEntityType type, const String & name) const override; std::vector findAllImpl(AccessEntityType type) const override; AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const override; - bool acquireReplicatedRestore(RestorerFromBackup & restorer) const override; mutable std::mutex mutex; MemoryAccessStorage memory_storage TSA_GUARDED_BY(mutex); diff --git a/src/Access/Role.cpp b/src/Access/Role.cpp index f6250594103..4140daa0fd9 100644 --- a/src/Access/Role.cpp +++ b/src/Access/Role.cpp @@ -21,10 +21,36 @@ std::vector Role::findDependencies() const return res; } -void Role::doReplaceDependencies(const std::unordered_map & old_to_new_ids) +bool Role::hasDependencies(const std::unordered_set & ids) const +{ + return granted_roles.hasDependencies(ids) || settings.hasDependencies(ids); +} + +void Role::replaceDependencies(const std::unordered_map & old_to_new_ids) { granted_roles.replaceDependencies(old_to_new_ids); settings.replaceDependencies(old_to_new_ids); } +void Role::copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) +{ + if (getType() != src.getType()) + return; + const auto & src_role = typeid_cast(src); + granted_roles.copyDependenciesFrom(src_role.granted_roles, ids); + settings.copyDependenciesFrom(src_role.settings, ids); +} + +void Role::removeDependencies(const std::unordered_set & ids) +{ + granted_roles.removeDependencies(ids); + settings.removeDependencies(ids); +} + +void Role::clearAllExceptDependencies() +{ + access = {}; + settings.removeSettingsKeepProfiles(); +} + } diff --git a/src/Access/Role.h b/src/Access/Role.h index c7f98585a6c..7b2450adfbe 100644 --- a/src/Access/Role.h +++ b/src/Access/Role.h @@ -21,7 +21,12 @@ struct Role : public IAccessEntity AccessEntityType getType() const override { return TYPE; } std::vector findDependencies() const override; - void doReplaceDependencies(const std::unordered_map & old_to_new_ids) override; + bool hasDependencies(const std::unordered_set & ids) const override; + void replaceDependencies(const std::unordered_map & old_to_new_ids) override; + void copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) override; + void removeDependencies(const std::unordered_set & ids) override; + void clearAllExceptDependencies() override; + bool isBackupAllowed() const override { return settings.isBackupAllowed(); } }; diff --git a/src/Access/RolesOrUsersSet.cpp b/src/Access/RolesOrUsersSet.cpp index c026ae42f76..3568b60e44d 100644 --- a/src/Access/RolesOrUsersSet.cpp +++ b/src/Access/RolesOrUsersSet.cpp @@ -295,6 +295,23 @@ std::vector RolesOrUsersSet::findDependencies() const return res; } +bool RolesOrUsersSet::hasDependencies(const std::unordered_set & dependencies_ids) const +{ + for (const auto & id : ids) + { + if (dependencies_ids.contains(id)) + return true; + } + + for (const auto & id : except_ids) + { + if (dependencies_ids.contains(id)) + return true; + } + + return false; +} + void RolesOrUsersSet::replaceDependencies(const std::unordered_map & old_to_new_ids) { std::vector new_ids; @@ -337,4 +354,41 @@ void RolesOrUsersSet::replaceDependencies(const std::unordered_map & boost::range::copy(new_ids, std::inserter(except_ids, except_ids.end())); } +void RolesOrUsersSet::copyDependenciesFrom(const RolesOrUsersSet & src, const std::unordered_set & dependencies_ids) +{ + if (all != src.all) + return; + + for (const auto & id : src.ids) + { + if (dependencies_ids.contains(id)) + ids.emplace(id); + } + + for (const auto & id : src.except_ids) + { + if (dependencies_ids.contains(id)) + except_ids.emplace(id); + } +} + +void RolesOrUsersSet::removeDependencies(const std::unordered_set & dependencies_ids) +{ + for (auto it = ids.begin(); it != ids.end();) + { + if (dependencies_ids.contains(*it)) + it = ids.erase(it); + else + ++it; + } + + for (auto it = except_ids.begin(); it != except_ids.end();) + { + if (dependencies_ids.contains(*it)) + except_ids.erase(it); + else + ++it; + } +} + } diff --git a/src/Access/RolesOrUsersSet.h b/src/Access/RolesOrUsersSet.h index 29e499bc81b..93d94708657 100644 --- a/src/Access/RolesOrUsersSet.h +++ b/src/Access/RolesOrUsersSet.h @@ -64,7 +64,10 @@ struct RolesOrUsersSet friend bool operator !=(const RolesOrUsersSet & lhs, const RolesOrUsersSet & rhs) { return !(lhs == rhs); } std::vector findDependencies() const; + bool hasDependencies(const std::unordered_set & dependencies_ids) const; void replaceDependencies(const std::unordered_map & old_to_new_ids); + void copyDependenciesFrom(const RolesOrUsersSet & src, const std::unordered_set & dependencies_ids); + void removeDependencies(const std::unordered_set & dependencies_ids); bool all = false; boost::container::flat_set ids; diff --git a/src/Access/RowPolicy.cpp b/src/Access/RowPolicy.cpp index 8724d0f513c..5b9d8521d9a 100644 --- a/src/Access/RowPolicy.cpp +++ b/src/Access/RowPolicy.cpp @@ -63,9 +63,33 @@ std::vector RowPolicy::findDependencies() const return to_roles.findDependencies(); } -void RowPolicy::doReplaceDependencies(const std::unordered_map & old_to_new_ids) +bool RowPolicy::hasDependencies(const std::unordered_set & ids) const +{ + return to_roles.hasDependencies(ids); +} + +void RowPolicy::replaceDependencies(const std::unordered_map & old_to_new_ids) { to_roles.replaceDependencies(old_to_new_ids); } +void RowPolicy::copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) +{ + if (getType() != src.getType()) + return; + const auto & src_policy = typeid_cast(src); + to_roles.copyDependenciesFrom(src_policy.to_roles, ids); +} + +void RowPolicy::removeDependencies(const std::unordered_set & ids) +{ + to_roles.removeDependencies(ids); +} + +void RowPolicy::clearAllExceptDependencies() +{ + for (auto & filter : filters) + filter = {}; +} + } diff --git a/src/Access/RowPolicy.h b/src/Access/RowPolicy.h index 5cfe85c186a..3ce96b24823 100644 --- a/src/Access/RowPolicy.h +++ b/src/Access/RowPolicy.h @@ -50,7 +50,12 @@ struct RowPolicy : public IAccessEntity AccessEntityType getType() const override { return TYPE; } std::vector findDependencies() const override; - void doReplaceDependencies(const std::unordered_map & old_to_new_ids) override; + bool hasDependencies(const std::unordered_set & ids) const override; + void replaceDependencies(const std::unordered_map & old_to_new_ids) override; + void copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) override; + void removeDependencies(const std::unordered_set & ids) override; + void clearAllExceptDependencies() override; + bool isBackupAllowed() const override { return true; } /// Which roles or users should use this row policy. diff --git a/src/Access/SettingsProfile.cpp b/src/Access/SettingsProfile.cpp index 632bd97fbf5..8f863b29e86 100644 --- a/src/Access/SettingsProfile.cpp +++ b/src/Access/SettingsProfile.cpp @@ -21,10 +21,35 @@ std::vector SettingsProfile::findDependencies() const return res; } -void SettingsProfile::doReplaceDependencies(const std::unordered_map & old_to_new_ids) +bool SettingsProfile::hasDependencies(const std::unordered_set & ids) const +{ + return elements.hasDependencies(ids) || to_roles.hasDependencies(ids); +} + +void SettingsProfile::replaceDependencies(const std::unordered_map & old_to_new_ids) { elements.replaceDependencies(old_to_new_ids); to_roles.replaceDependencies(old_to_new_ids); } +void SettingsProfile::copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) +{ + if (getType() != src.getType()) + return; + const auto & src_profile = typeid_cast(src); + elements.copyDependenciesFrom(src_profile.elements, ids); + to_roles.copyDependenciesFrom(src_profile.to_roles, ids); +} + +void SettingsProfile::removeDependencies(const std::unordered_set & ids) +{ + elements.removeDependencies(ids); + to_roles.removeDependencies(ids); +} + +void SettingsProfile::clearAllExceptDependencies() +{ + elements.removeSettingsKeepProfiles(); +} + } diff --git a/src/Access/SettingsProfile.h b/src/Access/SettingsProfile.h index 6bcaf6fef30..4e70a3d6727 100644 --- a/src/Access/SettingsProfile.h +++ b/src/Access/SettingsProfile.h @@ -22,7 +22,12 @@ struct SettingsProfile : public IAccessEntity AccessEntityType getType() const override { return TYPE; } std::vector findDependencies() const override; - void doReplaceDependencies(const std::unordered_map & old_to_new_ids) override; + bool hasDependencies(const std::unordered_set & ids) const override; + void replaceDependencies(const std::unordered_map & old_to_new_ids) override; + void copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) override; + void removeDependencies(const std::unordered_set & ids) override; + void clearAllExceptDependencies() override; + bool isBackupAllowed() const override { return elements.isBackupAllowed(); } }; diff --git a/src/Access/SettingsProfileElement.cpp b/src/Access/SettingsProfileElement.cpp index 156ffcf197d..1de61771f93 100644 --- a/src/Access/SettingsProfileElement.cpp +++ b/src/Access/SettingsProfileElement.cpp @@ -158,6 +158,18 @@ std::vector SettingsProfileElements::findDependencies() const } +bool SettingsProfileElements::hasDependencies(const std::unordered_set & ids) const +{ + std::vector res; + for (const auto & element : *this) + { + if (element.parent_profile && ids.contains(*element.parent_profile)) + return true; + } + return false; +} + + void SettingsProfileElements::replaceDependencies(const std::unordered_map & old_to_new_ids) { for (auto & element : *this) @@ -176,6 +188,38 @@ void SettingsProfileElements::replaceDependencies(const std::unordered_map & ids) +{ + SettingsProfileElements new_elements; + for (const auto & element : src) + { + if (element.parent_profile && ids.contains(*element.parent_profile)) + { + SettingsProfileElement new_element; + new_element.parent_profile = *element.parent_profile; + new_elements.emplace_back(new_element); + } + } + insert(begin(), new_elements.begin(), new_elements.end()); +} + + +void SettingsProfileElements::removeDependencies(const std::unordered_set & ids) +{ + std::erase_if( + *this, [&](const SettingsProfileElement & element) { return element.parent_profile && ids.contains(*element.parent_profile); }); +} + + +void SettingsProfileElements::removeSettingsKeepProfiles() +{ + for (auto & element : *this) + element.setting_name.clear(); + + std::erase_if(*this, [&](const SettingsProfileElement & element) { return element.setting_name.empty() && !element.parent_profile; }); +} + + void SettingsProfileElements::merge(const SettingsProfileElements & other) { insert(end(), other.begin(), other.end()); diff --git a/src/Access/SettingsProfileElement.h b/src/Access/SettingsProfileElement.h index 7078f565295..ea42b823ce1 100644 --- a/src/Access/SettingsProfileElement.h +++ b/src/Access/SettingsProfileElement.h @@ -63,7 +63,12 @@ public: std::shared_ptr toASTWithNames(const AccessControl & access_control) const; std::vector findDependencies() const; + bool hasDependencies(const std::unordered_set & ids) const; void replaceDependencies(const std::unordered_map & old_to_new_ids); + void copyDependenciesFrom(const SettingsProfileElements & src, const std::unordered_set & ids); + void removeDependencies(const std::unordered_set & ids); + + void removeSettingsKeepProfiles(); void merge(const SettingsProfileElements & other); diff --git a/src/Access/User.cpp b/src/Access/User.cpp index 2052527f4ae..887abc213f9 100644 --- a/src/Access/User.cpp +++ b/src/Access/User.cpp @@ -49,7 +49,12 @@ std::vector User::findDependencies() const return res; } -void User::doReplaceDependencies(const std::unordered_map & old_to_new_ids) +bool User::hasDependencies(const std::unordered_set & ids) const +{ + return default_roles.hasDependencies(ids) || granted_roles.hasDependencies(ids) || grantees.hasDependencies(ids) || settings.hasDependencies(ids); +} + +void User::replaceDependencies(const std::unordered_map & old_to_new_ids) { default_roles.replaceDependencies(old_to_new_ids); granted_roles.replaceDependencies(old_to_new_ids); @@ -57,4 +62,33 @@ void User::doReplaceDependencies(const std::unordered_map & old_to_n settings.replaceDependencies(old_to_new_ids); } +void User::copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) +{ + if (getType() != src.getType()) + return; + const auto & src_user = typeid_cast(src); + default_roles.copyDependenciesFrom(src_user.default_roles, ids); + granted_roles.copyDependenciesFrom(src_user.granted_roles, ids); + grantees.copyDependenciesFrom(src_user.grantees, ids); + settings.copyDependenciesFrom(src_user.settings, ids); +} + +void User::removeDependencies(const std::unordered_set & ids) +{ + default_roles.removeDependencies(ids); + granted_roles.removeDependencies(ids); + grantees.removeDependencies(ids); + settings.removeDependencies(ids); +} + +void User::clearAllExceptDependencies() +{ + authentication_methods.clear(); + allowed_client_hosts = AllowedClientHosts::AnyHostTag{}; + access = {}; + settings.removeSettingsKeepProfiles(); + default_database = {}; + valid_until = 0; +} + } diff --git a/src/Access/User.h b/src/Access/User.h index 7f91c1e3756..03d62bf2277 100644 --- a/src/Access/User.h +++ b/src/Access/User.h @@ -32,7 +32,12 @@ struct User : public IAccessEntity void setName(const String & name_) override; std::vector findDependencies() const override; - void doReplaceDependencies(const std::unordered_map & old_to_new_ids) override; + bool hasDependencies(const std::unordered_set & ids) const override; + void replaceDependencies(const std::unordered_map & old_to_new_ids) override; + void copyDependenciesFrom(const IAccessEntity & src, const std::unordered_set & ids) override; + void removeDependencies(const std::unordered_set & ids) override; + void clearAllExceptDependencies() override; + bool isBackupAllowed() const override { return settings.isBackupAllowed(); } }; diff --git a/src/Backups/BackupCoordinationRemote.cpp b/src/Backups/BackupCoordinationRemote.cpp index f353062f628..a60ac0c636f 100644 --- a/src/Backups/BackupCoordinationRemote.cpp +++ b/src/Backups/BackupCoordinationRemote.cpp @@ -570,7 +570,7 @@ void BackupCoordinationRemote::prepareReplicatedAccess() const if (replicated_access) return; - std::vector file_path_for_access_entities; + std::vector file_path_for_access_entities; auto holder = with_retries.createRetriesControlHolder("prepareReplicatedAccess"); holder.retries_ctl.retryLoop( [&, &zk = holder.faulty_zookeeper]() diff --git a/src/Backups/BackupCoordinationReplicatedAccess.cpp b/src/Backups/BackupCoordinationReplicatedAccess.cpp index 5a7d049920c..075ccadcc5d 100644 --- a/src/Backups/BackupCoordinationReplicatedAccess.cpp +++ b/src/Backups/BackupCoordinationReplicatedAccess.cpp @@ -1,5 +1,9 @@ #include +#include + +namespace fs = std::filesystem; + namespace DB { @@ -7,7 +11,7 @@ namespace DB BackupCoordinationReplicatedAccess::BackupCoordinationReplicatedAccess() = default; BackupCoordinationReplicatedAccess::~BackupCoordinationReplicatedAccess() = default; -void BackupCoordinationReplicatedAccess::addFilePath(FilePathForAccessEntitry && file_path_for_access_entity) +void BackupCoordinationReplicatedAccess::addFilePath(FilePathForAccessEntity && file_path_for_access_entity) { const auto & access_zk_path = file_path_for_access_entity.access_zk_path; const auto & access_entity_type = file_path_for_access_entity.access_entity_type; @@ -28,10 +32,19 @@ Strings BackupCoordinationReplicatedAccess::getFilePaths(const String & access_z return {}; const auto & file_paths = it->second; - if (file_paths.host_to_store_access != host_id) + if ((file_paths.host_to_store_access != host_id) || file_paths.file_paths.empty()) return {}; - Strings res{file_paths.file_paths.begin(), file_paths.file_paths.end()}; + /// Use the same filename for all the paths in backup. + /// Those filenames have format "access-.txt", where UUID is random. + /// It's not really necessary, however it looks better if those files have the same filename + /// for a backup of ReplicatedAccessStorage on different hosts. + Strings res; + res.reserve(file_paths.file_paths.size()); + String filename = fs::path{*file_paths.file_paths.begin()}.filename(); + for (const auto & file_path : file_paths.file_paths) + res.emplace_back(fs::path{file_path}.replace_filename(filename)); + return res; } diff --git a/src/Backups/BackupCoordinationReplicatedAccess.h b/src/Backups/BackupCoordinationReplicatedAccess.h index 23672c3799e..34a7c915588 100644 --- a/src/Backups/BackupCoordinationReplicatedAccess.h +++ b/src/Backups/BackupCoordinationReplicatedAccess.h @@ -2,7 +2,7 @@ #include #include -#include +#include namespace DB @@ -28,7 +28,7 @@ public: BackupCoordinationReplicatedAccess(); ~BackupCoordinationReplicatedAccess(); - struct FilePathForAccessEntitry + struct FilePathForAccessEntity { String access_zk_path; AccessEntityType access_entity_type; @@ -37,7 +37,7 @@ public: }; /// Adds a path to access*.txt file keeping access entities of a ReplicatedAccessStorage. - void addFilePath(FilePathForAccessEntitry && file_path_for_access_entity); + void addFilePath(FilePathForAccessEntity && file_path_for_access_entity); /// Returns all paths added by addFilePath() if `host_id` is a host chosen to store access. Strings getFilePaths(const String & access_zk_path, AccessEntityType access_entity_type, const String & host_id) const; @@ -47,7 +47,7 @@ private: struct FilePathsAndHost { - std::unordered_set file_paths; + std::set file_paths; String host_to_store_access; }; diff --git a/src/Backups/BackupCoordinationStage.h b/src/Backups/BackupCoordinationStage.h index 0a414a59a77..9abdc019784 100644 --- a/src/Backups/BackupCoordinationStage.h +++ b/src/Backups/BackupCoordinationStage.h @@ -32,6 +32,9 @@ namespace BackupCoordinationStage /// Finding databases and tables in the backup which we're going to restore. constexpr const char * FINDING_TABLES_IN_BACKUP = "finding tables in backup"; + /// Loading system access tables and then checking if the current user has enough access to restore. + constexpr const char * CHECKING_ACCESS_RIGHTS = "checking access rights"; + /// Creating databases or finding them and checking their definitions. constexpr const char * CREATING_DATABASES = "creating databases"; diff --git a/src/Backups/BackupEntriesCollector.cpp b/src/Backups/BackupEntriesCollector.cpp index 2024d7772f9..752691dd9ce 100644 --- a/src/Backups/BackupEntriesCollector.cpp +++ b/src/Backups/BackupEntriesCollector.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -910,11 +911,20 @@ void BackupEntriesCollector::runPostTasks() LOG_TRACE(log, "All post tasks successfully executed"); } -size_t BackupEntriesCollector::getAccessCounter(AccessEntityType type) +std::unordered_map BackupEntriesCollector::getAllAccessEntities() { std::lock_guard lock(mutex); - access_counters.resize(static_cast(AccessEntityType::MAX)); - return access_counters[static_cast(type)]++; + if (!all_access_entities) + { + all_access_entities.emplace(); + auto entities_with_ids = context->getAccessControl().readAllWithIDs(); + for (const auto & [id, entity] : entities_with_ids) + { + if (entity->isBackupAllowed()) + all_access_entities->emplace(id, entity); + } + } + return *all_access_entities; } } diff --git a/src/Backups/BackupEntriesCollector.h b/src/Backups/BackupEntriesCollector.h index fda5774105e..ae076a84c8b 100644 --- a/src/Backups/BackupEntriesCollector.h +++ b/src/Backups/BackupEntriesCollector.h @@ -21,7 +21,8 @@ class IBackupCoordination; class IDatabase; using DatabasePtr = std::shared_ptr; struct StorageID; -enum class AccessEntityType : uint8_t; +struct IAccessEntity; +using AccessEntityPtr = std::shared_ptr; class QueryStatus; using QueryStatusPtr = std::shared_ptr; @@ -49,6 +50,9 @@ public: ContextPtr getContext() const { return context; } const ZooKeeperRetriesInfo & getZooKeeperRetriesInfo() const { return global_zookeeper_retries_info; } + /// Returns all access entities which can be put into a backup. + std::unordered_map getAllAccessEntities(); + /// Adds a backup entry which will be later returned by run(). /// These function can be called by implementations of IStorage::backupData() in inherited storage classes. void addBackupEntry(const String & file_name, BackupEntryPtr backup_entry); @@ -61,9 +65,6 @@ public: /// 1) we need to join (in a backup) the data of replicated tables gathered on different hosts. void addPostTask(std::function task); - /// Returns an incremental counter used to backup access control. - size_t getAccessCounter(AccessEntityType type); - private: void calculateRootPathInBackup(); @@ -177,9 +178,10 @@ private: std::vector> previous_databases_metadata; std::vector> previous_tables_metadata; + std::optional> all_access_entities; + BackupEntries backup_entries; std::queue> post_tasks; - std::vector access_counters; ThreadPool & threadpool; std::mutex mutex; diff --git a/src/Backups/BackupSettings.cpp b/src/Backups/BackupSettings.cpp index e982a806b7c..9b8117c6587 100644 --- a/src/Backups/BackupSettings.cpp +++ b/src/Backups/BackupSettings.cpp @@ -37,6 +37,7 @@ namespace ErrorCodes M(Bool, check_parts) \ M(Bool, check_projection_parts) \ M(Bool, allow_backup_broken_projections) \ + M(Bool, write_access_entities_dependents) \ M(Bool, internal) \ M(String, host_id) \ M(OptionalUUID, backup_uuid) diff --git a/src/Backups/BackupSettings.h b/src/Backups/BackupSettings.h index 0abeb897db4..8c2ea21df01 100644 --- a/src/Backups/BackupSettings.h +++ b/src/Backups/BackupSettings.h @@ -77,6 +77,11 @@ struct BackupSettings /// Allow to create backup with broken projections. bool allow_backup_broken_projections = false; + /// Whether dependents of access entities should be written along with the access entities. + /// For example, if a role is granted to a user and we're making a backup of system.roles (but not system.users) + /// this is whether the backup will contain information to grant the role to the corresponding user again. + bool write_access_entities_dependents = true; + /// Internal, should not be specified by user. /// Whether this backup is a part of a distributed backup created by BACKUP ON CLUSTER. bool internal = false; diff --git a/src/Backups/RestoreSettings.cpp b/src/Backups/RestoreSettings.cpp index 8e60e8d129e..a8f6607e413 100644 --- a/src/Backups/RestoreSettings.cpp +++ b/src/Backups/RestoreSettings.cpp @@ -160,7 +160,8 @@ namespace M(UInt64, replica_num_in_backup) \ M(Bool, allow_non_empty_tables) \ M(RestoreAccessCreationMode, create_access) \ - M(Bool, allow_unresolved_access_dependencies) \ + M(Bool, skip_unresolved_access_dependencies) \ + M(Bool, update_access_entities_dependents) \ M(RestoreUDFCreationMode, create_function) \ M(Bool, allow_s3_native_copy) \ M(Bool, use_same_s3_credentials_for_base_backup) \ @@ -187,7 +188,12 @@ RestoreSettings RestoreSettings::fromRestoreQuery(const ASTBackupQuery & query) else LIST_OF_RESTORE_SETTINGS(GET_SETTINGS_FROM_RESTORE_QUERY_HELPER) - throw Exception(ErrorCodes::CANNOT_PARSE_BACKUP_SETTINGS, "Unknown setting {}", setting.name); + + /// `allow_unresolved_access_dependencies` is an obsolete name. + if (setting.name == "allow_unresolved_access_dependencies") + res.skip_unresolved_access_dependencies = SettingFieldBool{setting.value}.value; + else + throw Exception(ErrorCodes::CANNOT_PARSE_BACKUP_SETTINGS, "Unknown setting {}", setting.name); } } diff --git a/src/Backups/RestoreSettings.h b/src/Backups/RestoreSettings.h index fe07a0a7208..dd65ce3ea9c 100644 --- a/src/Backups/RestoreSettings.h +++ b/src/Backups/RestoreSettings.h @@ -100,9 +100,25 @@ struct RestoreSettings /// How the RESTORE command will handle if an user (or role or profile) which it's going to restore already exists. RestoreAccessCreationMode create_access = RestoreAccessCreationMode::kCreateIfNotExists; - /// Skip dependencies of access entities which can't be resolved. - /// For example, if an user has a profile assigned and that profile is not in the backup and doesn't exist locally. - bool allow_unresolved_access_dependencies = false; + /// Ignore dependencies or dependents (with update_access_entities_dependents=true) of access entities in the case if they can't be resolved. + /// For example: if a backup contains a profile assigned to a user: `CREATE PROFILE p1; CREATE USER u1 SETTINGS PROFILE p1` + /// and now we're restoring only user `u1` and profile `p1` doesn't exists, then + /// this flag is whether RESTORE should continue with restoring user `u1` without assigning profile `p1`. + /// Another example: if a backup contains a role granted to a user: `CREATE USER u2; CREATE ROLE r2; GRANT r2 TO u2` + /// and now we're restoring only user `u2` and role `r2` doesn't exist, then + /// this flag is whether RESTORE should continue with restoring user `u2` without that grant. + /// If this flag is false then RESTORE will throw an exception in that case. + bool skip_unresolved_access_dependencies = false; + + /// Try to update dependents of restored access entities. + /// For example: if a backup contains a profile assigned to a user: `CREATE PROFILE p1; CREATE USER u1 SETTINGS PROFILE p1` + /// and now we're restoring only profile `p1` and user `u1` already exists, then + /// this flag is whether restored profile `p1` should be assigned to user `u1` again. + /// Another example, if a backup contains a role granted to a user: `CREATE USER u2; CREATE ROLE r2; GRANT r2 TO u2` + /// and now we're restoring only role `r2` and user `u2` already exists, then + /// this flag is whether restored role `r2` should be granted to user `u2` again. + /// If this flag is false then RESTORE won't update existing access entities. + bool update_access_entities_dependents = true; /// How the RESTORE command will handle if a user-defined function which it's going to restore already exists. RestoreUDFCreationMode create_function = RestoreUDFCreationMode::kCreateIfNotExists; diff --git a/src/Backups/RestorerFromBackup.cpp b/src/Backups/RestorerFromBackup.cpp index 021d4208fb9..eb4ba9424ff 100644 --- a/src/Backups/RestorerFromBackup.cpp +++ b/src/Backups/RestorerFromBackup.cpp @@ -141,6 +141,8 @@ void RestorerFromBackup::run(Mode mode) waitFutures(); /// Check access rights. + setStage(Stage::CHECKING_ACCESS_RIGHTS); + loadSystemAccessTables(); checkAccessForObjectsFoundInBackup(); if (mode == Mode::CHECK_ACCESS_ONLY) @@ -487,25 +489,6 @@ void RestorerFromBackup::findTableInBackupImpl(const QualifiedTableName & table_ res_table_info.partitions.emplace(); insertAtEnd(*res_table_info.partitions, *partitions); } - - /// Special handling for ACL-related system tables. - if (!restore_settings.structure_only && isSystemAccessTableName(table_name)) - { - if (!access_restorer) - access_restorer = std::make_unique(backup, restore_settings); - - try - { - /// addDataPath() will parse access*.txt files and extract access entities from them. - /// We need to do that early because we need those access entities to check access. - access_restorer->addDataPath(data_path_in_backup); - } - catch (Exception & e) - { - e.addMessage("While parsing data of {} from backup", tableNameWithTypeToString(table_name.database, table_name.table, false)); - throw; - } - } } void RestorerFromBackup::findDatabaseInBackup(const String & database_name_in_backup, const std::set & except_table_names) @@ -629,6 +612,27 @@ size_t RestorerFromBackup::getNumTables() const return table_infos.size(); } +void RestorerFromBackup::loadSystemAccessTables() +{ + if (restore_settings.structure_only) + return; + + /// Special handling for ACL-related system tables. + std::lock_guard lock{mutex}; + for (const auto & [table_name, table_info] : table_infos) + { + if (isSystemAccessTableName(table_name)) + { + if (!access_restorer) + access_restorer = std::make_unique(backup, restore_settings); + access_restorer->addDataPath(table_info.data_path_in_backup); + } + } + + if (access_restorer) + access_restorer->loadFromBackup(); +} + void RestorerFromBackup::checkAccessForObjectsFoundInBackup() const { AccessRightsElements required_access; @@ -713,6 +717,15 @@ void RestorerFromBackup::checkAccessForObjectsFoundInBackup() const context->checkAccess(required_access); } +AccessEntitiesToRestore RestorerFromBackup::getAccessEntitiesToRestore(const String & data_path_in_backup) const +{ + std::lock_guard lock{mutex}; + if (!access_restorer) + return {}; + access_restorer->generateRandomIDsAndResolveDependencies(context->getAccessControl()); + return access_restorer->getEntitiesToRestore(data_path_in_backup); +} + void RestorerFromBackup::createDatabases() { Strings database_names; @@ -1071,19 +1084,6 @@ void RestorerFromBackup::runDataRestoreTasks() } } -std::vector> RestorerFromBackup::getAccessEntitiesToRestore() -{ - std::lock_guard lock{mutex}; - - if (!access_restorer || access_restored) - return {}; - - /// getAccessEntitiesToRestore() will return entities only when called first time (we don't want to restore the same entities again). - access_restored = true; - - return access_restorer->getAccessEntities(context->getAccessControl()); -} - void RestorerFromBackup::throwTableIsNotEmpty(const StorageID & storage_id) { throw Exception( diff --git a/src/Backups/RestorerFromBackup.h b/src/Backups/RestorerFromBackup.h index 6dbca5bced5..e0130ccfcb4 100644 --- a/src/Backups/RestorerFromBackup.h +++ b/src/Backups/RestorerFromBackup.h @@ -20,8 +20,7 @@ struct StorageID; class IDatabase; using DatabasePtr = std::shared_ptr; class AccessRestorerFromBackup; -struct IAccessEntity; -using AccessEntityPtr = std::shared_ptr; +struct AccessEntitiesToRestore; class QueryStatus; using QueryStatusPtr = std::shared_ptr; @@ -68,7 +67,7 @@ public: void addDataRestoreTasks(DataRestoreTasks && new_tasks); /// Returns the list of access entities to restore. - std::vector> getAccessEntitiesToRestore(); + AccessEntitiesToRestore getAccessEntitiesToRestore(const String & data_path_in_backup) const; /// Throws an exception that a specified table is already non-empty. [[noreturn]] static void throwTableIsNotEmpty(const StorageID & storage_id); @@ -101,6 +100,7 @@ private: size_t getNumDatabases() const; size_t getNumTables() const; + void loadSystemAccessTables(); void checkAccessForObjectsFoundInBackup() const; void createDatabases(); diff --git a/src/Interpreters/Access/InterpreterCreateQuotaQuery.cpp b/src/Interpreters/Access/InterpreterCreateQuotaQuery.cpp index 56608644425..fe5837f31dd 100644 --- a/src/Interpreters/Access/InterpreterCreateQuotaQuery.cpp +++ b/src/Interpreters/Access/InterpreterCreateQuotaQuery.cpp @@ -111,7 +111,7 @@ BlockIO InterpreterCreateQuotaQuery::execute() if (query.alter) { - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_quota = typeid_cast>(entity->clone()); updateQuotaFromQueryImpl(*updated_quota, query, {}, roles_from_query); diff --git a/src/Interpreters/Access/InterpreterCreateRoleQuery.cpp b/src/Interpreters/Access/InterpreterCreateRoleQuery.cpp index 4936bd15262..4c084309e37 100644 --- a/src/Interpreters/Access/InterpreterCreateRoleQuery.cpp +++ b/src/Interpreters/Access/InterpreterCreateRoleQuery.cpp @@ -74,7 +74,7 @@ BlockIO InterpreterCreateRoleQuery::execute() if (query.alter) { - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_role = typeid_cast>(entity->clone()); updateRoleFromQueryImpl(*updated_role, query, {}, settings_from_query); diff --git a/src/Interpreters/Access/InterpreterCreateRowPolicyQuery.cpp b/src/Interpreters/Access/InterpreterCreateRowPolicyQuery.cpp index b48c3880c59..8790d38b8d9 100644 --- a/src/Interpreters/Access/InterpreterCreateRowPolicyQuery.cpp +++ b/src/Interpreters/Access/InterpreterCreateRowPolicyQuery.cpp @@ -88,7 +88,7 @@ BlockIO InterpreterCreateRowPolicyQuery::execute() Strings names = query.names->toStrings(); if (query.alter) { - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_policy = typeid_cast>(entity->clone()); updateRowPolicyFromQueryImpl(*updated_policy, query, {}, roles_from_query); diff --git a/src/Interpreters/Access/InterpreterCreateSettingsProfileQuery.cpp b/src/Interpreters/Access/InterpreterCreateSettingsProfileQuery.cpp index 029deff9b22..aadc00d3e6f 100644 --- a/src/Interpreters/Access/InterpreterCreateSettingsProfileQuery.cpp +++ b/src/Interpreters/Access/InterpreterCreateSettingsProfileQuery.cpp @@ -90,7 +90,7 @@ BlockIO InterpreterCreateSettingsProfileQuery::execute() if (query.alter) { - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_profile = typeid_cast>(entity->clone()); updateSettingsProfileFromQueryImpl(*updated_profile, query, {}, settings_from_query, roles_from_query); diff --git a/src/Interpreters/Access/InterpreterCreateUserQuery.cpp b/src/Interpreters/Access/InterpreterCreateUserQuery.cpp index 81600b2b6eb..adb4bc2283a 100644 --- a/src/Interpreters/Access/InterpreterCreateUserQuery.cpp +++ b/src/Interpreters/Access/InterpreterCreateUserQuery.cpp @@ -264,7 +264,7 @@ BlockIO InterpreterCreateUserQuery::execute() if (query.grantees) grantees_from_query = RolesOrUsersSet{*query.grantees, access_control}; - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_user = typeid_cast>(entity->clone()); updateUserFromQueryImpl( @@ -317,7 +317,7 @@ BlockIO InterpreterCreateUserQuery::execute() if (query.grantees) { RolesOrUsersSet grantees_from_query = RolesOrUsersSet{*query.grantees, access_control}; - access_control.update(ids, [&](const AccessEntityPtr & entity) -> AccessEntityPtr + access_control.update(ids, [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_user = typeid_cast>(entity->clone()); updated_user->grantees = grantees_from_query; diff --git a/src/Interpreters/Access/InterpreterGrantQuery.cpp b/src/Interpreters/Access/InterpreterGrantQuery.cpp index ac3b549a576..99d574dba1e 100644 --- a/src/Interpreters/Access/InterpreterGrantQuery.cpp +++ b/src/Interpreters/Access/InterpreterGrantQuery.cpp @@ -474,7 +474,7 @@ BlockIO InterpreterGrantQuery::execute() calculateCurrentGrantRightsWithIntersection(new_rights, current_user_access, elements_to_grant); /// Update roles and users listed in `grantees`. - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto clone = entity->clone(); if (query.current_grants) diff --git a/src/Interpreters/Access/InterpreterSetRoleQuery.cpp b/src/Interpreters/Access/InterpreterSetRoleQuery.cpp index 99a7a73d46c..78926c3a0c9 100644 --- a/src/Interpreters/Access/InterpreterSetRoleQuery.cpp +++ b/src/Interpreters/Access/InterpreterSetRoleQuery.cpp @@ -46,7 +46,7 @@ void InterpreterSetRoleQuery::setDefaultRole(const ASTSetRoleQuery & query) std::vector to_users = RolesOrUsersSet{*query.to_users, access_control, getContext()->getUserID()}.getMatchingIDs(access_control); RolesOrUsersSet roles_from_query{*query.roles, access_control}; - auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr + auto update_func = [&](const AccessEntityPtr & entity, const UUID &) -> AccessEntityPtr { auto updated_user = typeid_cast>(entity->clone()); updateUserSetDefaultRoles(*updated_user, roles_from_query); diff --git a/src/Storages/System/StorageSystemQuotas.cpp b/src/Storages/System/StorageSystemQuotas.cpp index 641bbb319d5..ddd86f8736b 100644 --- a/src/Storages/System/StorageSystemQuotas.cpp +++ b/src/Storages/System/StorageSystemQuotas.cpp @@ -150,10 +150,10 @@ void StorageSystemQuotas::backupData( } void StorageSystemQuotas::restoreDataFromBackup( - RestorerFromBackup & restorer, const String & /* data_path_in_backup */, const std::optional & /* partitions */) + RestorerFromBackup & restorer, const String & data_path_in_backup, const std::optional & /* partitions */) { auto & access_control = restorer.getContext()->getAccessControl(); - access_control.restoreFromBackup(restorer); + access_control.restoreFromBackup(restorer, data_path_in_backup); } } diff --git a/src/Storages/System/StorageSystemRoles.cpp b/src/Storages/System/StorageSystemRoles.cpp index 9bfddc25ebf..2409580bbd2 100644 --- a/src/Storages/System/StorageSystemRoles.cpp +++ b/src/Storages/System/StorageSystemRoles.cpp @@ -70,10 +70,10 @@ void StorageSystemRoles::backupData( } void StorageSystemRoles::restoreDataFromBackup( - RestorerFromBackup & restorer, const String & /* data_path_in_backup */, const std::optional & /* partitions */) + RestorerFromBackup & restorer, const String & data_path_in_backup, const std::optional & /* partitions */) { auto & access_control = restorer.getContext()->getAccessControl(); - access_control.restoreFromBackup(restorer); + access_control.restoreFromBackup(restorer, data_path_in_backup); } } diff --git a/src/Storages/System/StorageSystemRowPolicies.cpp b/src/Storages/System/StorageSystemRowPolicies.cpp index 93c5ba60a7f..759535e33b7 100644 --- a/src/Storages/System/StorageSystemRowPolicies.cpp +++ b/src/Storages/System/StorageSystemRowPolicies.cpp @@ -160,10 +160,10 @@ void StorageSystemRowPolicies::backupData( } void StorageSystemRowPolicies::restoreDataFromBackup( - RestorerFromBackup & restorer, const String & /* data_path_in_backup */, const std::optional & /* partitions */) + RestorerFromBackup & restorer, const String & data_path_in_backup, const std::optional & /* partitions */) { auto & access_control = restorer.getContext()->getAccessControl(); - access_control.restoreFromBackup(restorer); + access_control.restoreFromBackup(restorer, data_path_in_backup); } } diff --git a/src/Storages/System/StorageSystemSettingsProfiles.cpp b/src/Storages/System/StorageSystemSettingsProfiles.cpp index 795152e31f3..7aa9fae9ebc 100644 --- a/src/Storages/System/StorageSystemSettingsProfiles.cpp +++ b/src/Storages/System/StorageSystemSettingsProfiles.cpp @@ -101,10 +101,10 @@ void StorageSystemSettingsProfiles::backupData( } void StorageSystemSettingsProfiles::restoreDataFromBackup( - RestorerFromBackup & restorer, const String & /* data_path_in_backup */, const std::optional & /* partitions */) + RestorerFromBackup & restorer, const String & data_path_in_backup, const std::optional & /* partitions */) { auto & access_control = restorer.getContext()->getAccessControl(); - access_control.restoreFromBackup(restorer); + access_control.restoreFromBackup(restorer, data_path_in_backup); } } diff --git a/src/Storages/System/StorageSystemUsers.cpp b/src/Storages/System/StorageSystemUsers.cpp index ce4950f5e7b..7589afdeb3e 100644 --- a/src/Storages/System/StorageSystemUsers.cpp +++ b/src/Storages/System/StorageSystemUsers.cpp @@ -261,10 +261,10 @@ void StorageSystemUsers::backupData( } void StorageSystemUsers::restoreDataFromBackup( - RestorerFromBackup & restorer, const String & /* data_path_in_backup */, const std::optional & /* partitions */) + RestorerFromBackup & restorer, const String & data_path_in_backup, const std::optional & /* partitions */) { auto & access_control = restorer.getContext()->getAccessControl(); - access_control.restoreFromBackup(restorer); + access_control.restoreFromBackup(restorer, data_path_in_backup); } } diff --git a/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.reference b/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.reference new file mode 100644 index 00000000000..8268df7a65b --- /dev/null +++ b/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.reference @@ -0,0 +1,7 @@ +CREATE USER user_03231 IDENTIFIED WITH no_password DEFAULT ROLE role_a_03231 SETTINGS custom_x = \'x\' +GRANT role_a_03231 TO user_03231 +CREATE ROLE role_a_03231 +GRANT INSERT ON *.* TO role_a_03231 +GRANT role_b_03231 TO role_a_03231 +CREATE ROLE role_b_03231 +GRANT SELECT ON *.* TO role_b_03231 diff --git a/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.sh b/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.sh new file mode 100755 index 00000000000..550db582b30 --- /dev/null +++ b/tests/queries/0_stateless/03231_old_backup_without_access_entities_dependents.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel +# Tag no-fasttest: we restore from a zip-archived backup here. +# Tag no-parallel: we drop and restore fixed users and roles. + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +# In this test we restore from "/tests/queries/0_stateless/backups/old_backup_without_access_entities_dependents.zip" +backup_name="$($CURDIR/helpers/install_predefined_backup.sh old_backup_without_access_entities_dependents.zip)" + +${CLICKHOUSE_CLIENT} -m --query " +DROP USER IF EXISTS user_03231; +DROP ROLE IF EXISTS role_a_03231, role_b_03231; +" + +${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM Disk('backups', '${backup_name}') FORMAT Null" + +${CLICKHOUSE_CLIENT} --query "SHOW CREATE USER user_03231" +${CLICKHOUSE_CLIENT} --query "SHOW GRANTS FOR user_03231" +${CLICKHOUSE_CLIENT} --query "SHOW CREATE ROLE role_a_03231" +${CLICKHOUSE_CLIENT} --query "SHOW GRANTS FOR role_a_03231" +${CLICKHOUSE_CLIENT} --query "SHOW CREATE ROLE role_b_03231" +${CLICKHOUSE_CLIENT} --query "SHOW GRANTS FOR role_b_03231" + +${CLICKHOUSE_CLIENT} -m --query " +DROP USER user_03231; +DROP ROLE role_a_03231, role_b_03231; +" diff --git a/tests/queries/0_stateless/03231_restore_user_with_existing_role.reference b/tests/queries/0_stateless/03231_restore_user_with_existing_role.reference index cad1bf13574..70ff4078811 100644 --- a/tests/queries/0_stateless/03231_restore_user_with_existing_role.reference +++ b/tests/queries/0_stateless/03231_restore_user_with_existing_role.reference @@ -1,6 +1,13 @@ Everything dropped User dropped +Role dropped Nothing dropped Nothing dropped, mode=replace Nothing dropped, mode=create ACCESS_ENTITY_ALREADY_EXISTS +Everything dropped, restore system.roles, then system.users +user_a 0 +role_b 1 +Everything dropped, restore system.users, then system.roles +user_a 1 +role_b 0 diff --git a/tests/queries/0_stateless/03231_restore_user_with_existing_role.sh b/tests/queries/0_stateless/03231_restore_user_with_existing_role.sh index 04f907b719d..e24ce4a33f5 100755 --- a/tests/queries/0_stateless/03231_restore_user_with_existing_role.sh +++ b/tests/queries/0_stateless/03231_restore_user_with_existing_role.sh @@ -55,14 +55,10 @@ ${CLICKHOUSE_CLIENT} --query "DROP USER ${user_a}" ${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM ${backup_name} FORMAT Null" do_check -# TODO: Cannot restore a dropped role granted to an existing user. The result after RESTORE ALL below is the following: -# CREATE USER user_a DEFAULT ROLE NONE SETTINGS custom_x = 2; GRANT NONE TO user_a; CREATE ROLE role_b SETTINGS custom_x = 1 -# because `role_b` is restored but not granted to existing user `user_a`. -# -# echo "Role dropped" -# ${CLICKHOUSE_CLIENT} --query "DROP ROLE ${role_b}" -# ${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM ${backup_name} FORMAT Null" -# do_check +echo "Role dropped" +${CLICKHOUSE_CLIENT} --query "DROP ROLE ${role_b}" +${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM ${backup_name} FORMAT Null" +do_check echo "Nothing dropped" ${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM ${backup_name} FORMAT Null" @@ -75,3 +71,23 @@ do_check echo "Nothing dropped, mode=create" ${CLICKHOUSE_CLIENT} --query "RESTORE ALL FROM ${backup_name} SETTINGS create_access='create' FORMAT Null" 2>&1 | grep -om1 "ACCESS_ENTITY_ALREADY_EXISTS" do_check + +echo "Everything dropped, restore system.roles, then system.users" +${CLICKHOUSE_CLIENT} --query "DROP USER ${user_a}" +${CLICKHOUSE_CLIENT} --query "DROP ROLE ${role_b}" +# Here "skip_unresolved_access_dependencies=true" because users don't exist yet and restored roles can't be granted to non-existent users. +${CLICKHOUSE_CLIENT} --query "RESTORE TABLE system.roles FROM ${backup_name} SETTINGS skip_unresolved_access_dependencies=true FORMAT Null" +${CLICKHOUSE_CLIENT} --query "SELECT 'user_a', count() FROM system.users WHERE name = '${user_a}'" +${CLICKHOUSE_CLIENT} --query "SELECT 'role_b', count() FROM system.roles WHERE name = '${role_b}'" +${CLICKHOUSE_CLIENT} --query "RESTORE TABLE system.users FROM ${backup_name} FORMAT Null" +do_check + +echo "Everything dropped, restore system.users, then system.roles" +${CLICKHOUSE_CLIENT} --query "DROP USER ${user_a}" +${CLICKHOUSE_CLIENT} --query "DROP ROLE ${role_b}" +# Here "skip_unresolved_access_dependencies=true" because roles don't exist yet and can't be granted to restored users. +${CLICKHOUSE_CLIENT} --query "RESTORE TABLE system.users FROM ${backup_name} SETTINGS skip_unresolved_access_dependencies=true FORMAT Null" +${CLICKHOUSE_CLIENT} --query "SELECT 'user_a', count() FROM system.users WHERE name = '${user_a}'" +${CLICKHOUSE_CLIENT} --query "SELECT 'role_b', count() FROM system.roles WHERE name = '${role_b}'" +${CLICKHOUSE_CLIENT} --query "RESTORE TABLE system.roles FROM ${backup_name} FORMAT Null" +do_check diff --git a/tests/queries/0_stateless/backups/old_backup_without_access_entities_dependents.zip b/tests/queries/0_stateless/backups/old_backup_without_access_entities_dependents.zip new file mode 100644 index 0000000000000000000000000000000000000000..a20e66b2f6b8294fcd81323c467e36064caa7f20 GIT binary patch literal 1996 zcmWIWW@Zs#VB+9ln8|G%sVi%5r_IQ~u#|~`ft`VYK`$vWIlHuA>ZNSI!v;JI4^|g6 z>XqG_xhRfxXD74hYqM!0jLVlx_jB?J{7yeQ&CXuva$fYY-F9{#7xwMW?GLPJGc2C> zeYRoXDXnal1|`WU|-a-8u*W=7b&WRG9#3{SqT;(Tl(wkE}7?V7c(ge#dg zvHTXd{1%#ha;L0MzUTExHPeM}|9vB8CK+zCsE?!7iA8rJ6W2)xHv82QTOI|^%)Q=! zsh&?<>fP+N-xj$By(`0|-se@Z;|??QkSHxqEkX|orE>Mu^Eg5Rq8>RUrcN--K5QV; zlKwk<&)KsI9XcnwIAnePOyXz?ej{!r6+LPBHoY?EPoEzm9de* zz3jtNo=>WrZ#7LSzeMfortHd+RU5m`1xw6c>cQGyyr^b#`)*!)vl~p_o1(i}_e9+~ zeEdSqw#y}wZv3oypD#Bqu#zj_vs0~ApZjuZ(xH!$m;4M~h8%aA{ZpZ*tVnbc>lKfb z)3LnoY-)MmpB0^_xKr(Qep~F+>0jnMS^aa0J6_nRA93!1ba&~W>3uKk6s6j?U)a)b zpNBi>IDtWun_7}cAXN$=D?>?{u#|S@_(m=*pej8emc*hO9L9RZg*nB1MWsB2p&W%J zp9(`Y*6HYZp1rEA>!+&~R`9j>`Y-` zbx|x}YXn)q$RxsyyXXUY0}K{4f+%P~2-Acse?zngFuZUK0A@O9xI*l~rUjJW5n762 zTHs-fTPG;hBXrJ1(+MjLP<@DA9>7dk(0CTBe&jrX&*P|WSq`i^z#fPC0+t_9-GZJY z5pKDTRX?$A0VQb&07b$hW|RbuJ2*i}9HEVq1xXuPWWZ7+dXk6fU(jfdMK?SHz`_w` h40^&w7&8NlG4Ny_;LXYgQpyd4dw~J=6R4Ph0RUw8O-KL$ literal 0 HcmV?d00001