#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int BAD_ARGUMENTS; extern const int UNKNOWN_ADDRESS_PATTERN_TYPE; extern const int NOT_IMPLEMENTED; } namespace { char getTypeChar(std::type_index type) { if (type == typeid(User)) return 'U'; if (type == typeid(Quota)) return 'Q'; if (type == typeid(RowPolicy)) return 'P'; if (type == typeid(SettingsProfile)) return 'S'; return 0; } UUID generateID(std::type_index type, const String & name) { Poco::MD5Engine md5; md5.update(name); char type_storage_chars[] = " USRSXML"; type_storage_chars[0] = getTypeChar(type); md5.update(type_storage_chars, strlen(type_storage_chars)); UUID result; memcpy(&result, md5.digest().data(), md5.digestLength()); return result; } UUID generateID(const IAccessEntity & entity) { return generateID(entity.getType(), entity.getFullName()); } UserPtr parseUser(const Poco::Util::AbstractConfiguration & config, const String & user_name) { auto user = std::make_shared(); user->setName(user_name); String user_config = "users." + user_name; bool has_password = config.has(user_config + ".password"); bool has_password_sha256_hex = config.has(user_config + ".password_sha256_hex"); bool has_password_double_sha1_hex = config.has(user_config + ".password_double_sha1_hex"); if (has_password + has_password_sha256_hex + has_password_double_sha1_hex > 1) throw Exception("More than one field of 'password', 'password_sha256_hex', 'password_double_sha1_hex' is used to specify password for user " + user_name + ". Must be only one of them.", ErrorCodes::BAD_ARGUMENTS); if (!has_password && !has_password_sha256_hex && !has_password_double_sha1_hex) throw Exception("Either 'password' or 'password_sha256_hex' or 'password_double_sha1_hex' must be specified for user " + user_name + ".", ErrorCodes::BAD_ARGUMENTS); if (has_password) { user->authentication = Authentication{Authentication::PLAINTEXT_PASSWORD}; user->authentication.setPassword(config.getString(user_config + ".password")); } else if (has_password_sha256_hex) { user->authentication = Authentication{Authentication::SHA256_PASSWORD}; user->authentication.setPasswordHashHex(config.getString(user_config + ".password_sha256_hex")); } else if (has_password_double_sha1_hex) { user->authentication = Authentication{Authentication::DOUBLE_SHA1_PASSWORD}; user->authentication.setPasswordHashHex(config.getString(user_config + ".password_double_sha1_hex")); } const auto profile_name_config = user_config + ".profile"; if (config.has(profile_name_config)) { auto profile_name = config.getString(profile_name_config); SettingsProfileElement profile_element; profile_element.parent_profile = generateID(typeid(SettingsProfile), profile_name); user->settings.push_back(std::move(profile_element)); } /// Fill list of allowed hosts. const auto networks_config = user_config + ".networks"; if (config.has(networks_config)) { Poco::Util::AbstractConfiguration::Keys keys; config.keys(networks_config, keys); user->allowed_client_hosts.clear(); for (const String & key : keys) { String value = config.getString(networks_config + "." + key); if (key.starts_with("ip")) user->allowed_client_hosts.addSubnet(value); else if (key.starts_with("host_regexp")) user->allowed_client_hosts.addNameRegexp(value); else if (key.starts_with("host")) user->allowed_client_hosts.addName(value); else throw Exception("Unknown address pattern type: " + key, ErrorCodes::UNKNOWN_ADDRESS_PATTERN_TYPE); } } /// Fill list of allowed databases. const auto databases_config = user_config + ".allow_databases"; std::optional databases; if (config.has(databases_config)) { Poco::Util::AbstractConfiguration::Keys keys; config.keys(databases_config, keys); databases.emplace(); databases->reserve(keys.size()); for (const auto & key : keys) { const auto database_name = config.getString(databases_config + "." + key); databases->push_back(database_name); } } /// Fill list of allowed dictionaries. const auto dictionaries_config = user_config + ".allow_dictionaries"; std::optional dictionaries; if (config.has(dictionaries_config)) { Poco::Util::AbstractConfiguration::Keys keys; config.keys(dictionaries_config, keys); dictionaries.emplace(); dictionaries->reserve(keys.size()); for (const auto & key : keys) { const auto dictionary_name = config.getString(dictionaries_config + "." + key); dictionaries->push_back(dictionary_name); } } user->access.grant(AccessType::ALL); /// By default all databases are accessible. if (databases) { user->access.revoke(AccessFlags::allFlags() - AccessFlags::allGlobalFlags()); user->access.grant(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG); for (const String & database : *databases) user->access.grant(AccessFlags::allFlags(), database); } if (dictionaries) { user->access.revoke(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG); for (const String & dictionary : *dictionaries) user->access.grant(AccessFlags::allDictionaryFlags(), IDictionary::NO_DATABASE_TAG, dictionary); } user->access_with_grant_option = user->access; return user; } std::vector parseUsers(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) { Poco::Util::AbstractConfiguration::Keys user_names; config.keys("users", user_names); std::vector users; users.reserve(user_names.size()); for (const auto & user_name : user_names) { try { users.push_back(parseUser(config, user_name)); } catch (...) { tryLogCurrentException(log, "Could not parse user " + backQuote(user_name)); } } return users; } QuotaPtr parseQuota(const Poco::Util::AbstractConfiguration & config, const String & quota_name, const std::vector & user_ids) { auto quota = std::make_shared(); quota->setName(quota_name); using KeyType = Quota::KeyType; String quota_config = "quotas." + quota_name; if (config.has(quota_config + ".keyed_by_ip")) quota->key_type = KeyType::IP_ADDRESS; else if (config.has(quota_config + ".keyed")) quota->key_type = KeyType::CLIENT_KEY_OR_USER_NAME; else quota->key_type = KeyType::USER_NAME; Poco::Util::AbstractConfiguration::Keys interval_keys; config.keys(quota_config, interval_keys); for (const String & interval_key : interval_keys) { if (!startsWith(interval_key, "interval")) continue; String interval_config = quota_config + "." + interval_key; std::chrono::seconds duration{config.getInt(interval_config + ".duration", 0)}; if (duration.count() <= 0) /// Skip quotas with non-positive duration. continue; quota->all_limits.emplace_back(); auto & limits = quota->all_limits.back(); limits.duration = duration; limits.randomize_interval = config.getBool(interval_config + ".randomize", false); using ResourceType = Quota::ResourceType; limits.max[ResourceType::QUERIES] = config.getUInt64(interval_config + ".queries", Quota::UNLIMITED); limits.max[ResourceType::ERRORS] = config.getUInt64(interval_config + ".errors", Quota::UNLIMITED); limits.max[ResourceType::RESULT_ROWS] = config.getUInt64(interval_config + ".result_rows", Quota::UNLIMITED); limits.max[ResourceType::RESULT_BYTES] = config.getUInt64(interval_config + ".result_bytes", Quota::UNLIMITED); limits.max[ResourceType::READ_ROWS] = config.getUInt64(interval_config + ".read_rows", Quota::UNLIMITED); limits.max[ResourceType::READ_BYTES] = config.getUInt64(interval_config + ".read_bytes", Quota::UNLIMITED); limits.max[ResourceType::EXECUTION_TIME] = Quota::secondsToExecutionTime(config.getUInt64(interval_config + ".execution_time", Quota::UNLIMITED)); } quota->to_roles.add(user_ids); return quota; } std::vector parseQuotas(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) { Poco::Util::AbstractConfiguration::Keys user_names; config.keys("users", user_names); std::unordered_map> quota_to_user_ids; for (const auto & user_name : user_names) { if (config.has("users." + user_name + ".quota")) quota_to_user_ids[config.getString("users." + user_name + ".quota")].push_back(generateID(typeid(User), user_name)); } Poco::Util::AbstractConfiguration::Keys quota_names; config.keys("quotas", quota_names); std::vector quotas; quotas.reserve(quota_names.size()); for (const auto & quota_name : quota_names) { try { auto it = quota_to_user_ids.find(quota_name); const std::vector & quota_users = (it != quota_to_user_ids.end()) ? std::move(it->second) : std::vector{}; quotas.push_back(parseQuota(config, quota_name, quota_users)); } catch (...) { tryLogCurrentException(log, "Could not parse quota " + backQuote(quota_name)); } } return quotas; } std::vector parseRowPolicies(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) { std::map, std::unordered_map> all_filters_map; Poco::Util::AbstractConfiguration::Keys user_names; try { config.keys("users", user_names); for (const String & user_name : user_names) { const String databases_config = "users." + user_name + ".databases"; if (config.has(databases_config)) { Poco::Util::AbstractConfiguration::Keys databases; config.keys(databases_config, databases); /// Read tables within databases for (const String & database : databases) { const String database_config = databases_config + "." + database; Poco::Util::AbstractConfiguration::Keys keys_in_database_config; config.keys(database_config, keys_in_database_config); /// Read table properties for (const String & key_in_database_config : keys_in_database_config) { String table_name = key_in_database_config; String filter_config = database_config + "." + table_name + ".filter"; if (key_in_database_config.starts_with("table[")) { const auto table_name_config = database_config + "." + table_name + "[@name]"; if (config.has(table_name_config)) { table_name = config.getString(table_name_config); filter_config = database_config + ".table[@name='" + table_name + "']"; } } all_filters_map[{database, table_name}][user_name] = config.getString(filter_config); } } } } } catch (...) { tryLogCurrentException(log, "Could not parse row policies"); } std::vector policies; for (auto & [database_and_table_name, user_to_filters] : all_filters_map) { const auto & [database, table_name] = database_and_table_name; for (const String & user_name : user_names) { auto it = user_to_filters.find(user_name); String filter = (it != user_to_filters.end()) ? it->second : "1"; auto policy = std::make_shared(); policy->setFullName(database, table_name, user_name); policy->conditions[RowPolicy::SELECT_FILTER] = filter; policy->to_roles.add(generateID(typeid(User), user_name)); policies.push_back(policy); } } return policies; } SettingsProfileElements parseSettingsConstraints(const Poco::Util::AbstractConfiguration & config, const String & path_to_constraints) { SettingsProfileElements profile_elements; Poco::Util::AbstractConfiguration::Keys names; config.keys(path_to_constraints, names); for (const String & name : names) { SettingsProfileElement profile_element; profile_element.name = name; Poco::Util::AbstractConfiguration::Keys constraint_types; String path_to_name = path_to_constraints + "." + name; config.keys(path_to_name, constraint_types); for (const String & constraint_type : constraint_types) { if (constraint_type == "min") profile_element.min_value = config.getString(path_to_name + "." + constraint_type); else if (constraint_type == "max") profile_element.max_value = config.getString(path_to_name + "." + constraint_type); else if (constraint_type == "readonly") profile_element.readonly = true; else throw Exception("Setting " + constraint_type + " value for " + name + " isn't supported", ErrorCodes::NOT_IMPLEMENTED); } profile_elements.push_back(std::move(profile_element)); } return profile_elements; } std::shared_ptr parseSettingsProfile( const Poco::Util::AbstractConfiguration & config, const String & profile_name) { auto profile = std::make_shared(); profile->setName(profile_name); String profile_config = "profiles." + profile_name; Poco::Util::AbstractConfiguration::Keys keys; config.keys(profile_config, keys); for (const std::string & key : keys) { if (key == "profile" || key.starts_with("profile[")) { String parent_profile_name = config.getString(profile_config + "." + key); SettingsProfileElement profile_element; profile_element.parent_profile = generateID(typeid(SettingsProfile), parent_profile_name); profile->elements.emplace_back(std::move(profile_element)); continue; } if (key == "constraints" || key.starts_with("constraints[")) { profile->elements.merge(parseSettingsConstraints(config, profile_config + "." + key)); continue; } SettingsProfileElement profile_element; profile_element.name = key; profile_element.value = config.getString(profile_config + "." + key); profile->elements.emplace_back(std::move(profile_element)); } return profile; } std::vector parseSettingsProfiles(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) { std::vector profiles; Poco::Util::AbstractConfiguration::Keys profile_names; config.keys("profiles", profile_names); for (const auto & profile_name : profile_names) { try { profiles.push_back(parseSettingsProfile(config, profile_name)); } catch (...) { tryLogCurrentException(log, "Could not parse profile " + backQuote(profile_name)); } } return profiles; } } UsersConfigAccessStorage::UsersConfigAccessStorage() : IAccessStorage("users.xml") { } void UsersConfigAccessStorage::setConfiguration(const Poco::Util::AbstractConfiguration & config) { std::vector> all_entities; for (const auto & entity : parseUsers(config, getLogger())) all_entities.emplace_back(generateID(*entity), entity); for (const auto & entity : parseQuotas(config, getLogger())) all_entities.emplace_back(generateID(*entity), entity); for (const auto & entity : parseRowPolicies(config, getLogger())) all_entities.emplace_back(generateID(*entity), entity); for (const auto & entity : parseSettingsProfiles(config, getLogger())) all_entities.emplace_back(generateID(*entity), entity); memory_storage.setAll(all_entities); } std::optional UsersConfigAccessStorage::findImpl(std::type_index type, const String & name) const { return memory_storage.find(type, name); } std::vector UsersConfigAccessStorage::findAllImpl(std::type_index type) const { return memory_storage.findAll(type); } bool UsersConfigAccessStorage::existsImpl(const UUID & id) const { return memory_storage.exists(id); } AccessEntityPtr UsersConfigAccessStorage::readImpl(const UUID & id) const { return memory_storage.read(id); } String UsersConfigAccessStorage::readNameImpl(const UUID & id) const { return memory_storage.readName(id); } UUID UsersConfigAccessStorage::insertImpl(const AccessEntityPtr & entity, bool) { throwReadonlyCannotInsert(entity->getType(), entity->getFullName()); } void UsersConfigAccessStorage::removeImpl(const UUID & id) { auto entity = read(id); throwReadonlyCannotRemove(entity->getType(), entity->getFullName()); } void UsersConfigAccessStorage::updateImpl(const UUID & id, const UpdateFunc &) { auto entity = read(id); throwReadonlyCannotUpdate(entity->getType(), entity->getFullName()); } ext::scope_guard UsersConfigAccessStorage::subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const { return memory_storage.subscribeForChanges(id, handler); } ext::scope_guard UsersConfigAccessStorage::subscribeForChangesImpl(std::type_index type, const OnChangedHandler & handler) const { return memory_storage.subscribeForChanges(type, handler); } bool UsersConfigAccessStorage::hasSubscriptionImpl(const UUID & id) const { return memory_storage.hasSubscription(id); } bool UsersConfigAccessStorage::hasSubscriptionImpl(std::type_index type) const { return memory_storage.hasSubscription(type); } }