Do transformations based on prefix only

This commit is contained in:
Denis Glazachev 2020-12-17 18:27:30 +04:00
parent b6c2743103
commit 53db7e564c
4 changed files with 18 additions and 103 deletions

View File

@ -316,26 +316,9 @@
The resulting filter will be constructed by replacing all '{username}', '{bind_dn}', and '{base_dn}'
substrings of the template with the actual user name, bind DN, and base DN during each LDAP search.
Note, that the special characters must be escaped properly in XML.
fail_if_all_rules_mismatch - flag to trigger failure if none of the rules were able to match and transform any
of the resulting strings returned by the LDAP search. By default, set to 'true'.
rule - section with matching and mapping info.
Each string will be matched to the regex and, if there is a full match, replaced using format string to
get the name of the local role that needs to be assigned to the user.
There can be multiple 'rule' sections defined inside the same 'role_mapping' section. Each of those rules
will be applied in the order they are listed in 'role_mapping' section. If a rule does not match a string
the next rule will be applied. If none of the rules matched a string and 'fail_if_all_rules_mismatch' is
set to 'false', that particular string will be ignored. If a rule matched a string and 'continue_on_match'
is set to 'false', the subsequent rules will not be applied to the current string.
match - regular expression, in ECMAScript format, used to match each entire string retured by LDAP serach. If
matched successfully, a replacement will be performed and the resulting string will be treated as local
role name and assigned to the user. By default, set to '.+' (match any non-empty string).
Note, that the special characters must be escaped properly in XML.
replace - format string used as a replace expression after the match succeeded. References like '$&' (entire
matched string), or '$n' (n-th subgroup) can be used. By default, set to '$&'.
Note, that the special characters must be escaped properly in XML.
continue_on_match - flag that indicates, whether to continue matching and mapping using the subsequent rules
after this rule successfully matched and mapped the string. By default, set to 'false'.
If set to 'true' and multiple rules match, multiple role names may be generated from one same input string.
prefix - prefix, that will be expected to be in front of each string in the original list of strings returned by
the LDAP search. Prefix will be removed from the original strings and resulting strings will be treated
as local role names. Empty, by default.
Example:
<ldap>
<server>my_ldap_server</server>
@ -348,17 +331,7 @@
<attribute>cn</attribute>
<scope>subtree</scope>
<search_filter>(&amp;(objectClass=groupOfNames)(member={bind_dn}))</search_filter>
<fail_if_all_rules_mismatch>true</fail_if_all_rules_mismatch>
<rule>
<match>clickhouse_(.+)</match>
<replace>$1</replace>
<continue_on_match>true</continue_on_match>
</rule>
<rule>
<match>.+</match>
<replace>$&amp;</replace>
<continue_on_match>false</continue_on_match>
</rule>
<prefix>clickhouse_</prefix>
</role_mapping>
</ldap>
-->

View File

@ -13,7 +13,6 @@
#include <boost/container_hash/hash.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <iterator>
#include <regex>
#include <sstream>
#include <unordered_map>
@ -85,7 +84,7 @@ void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_m
rm_params.base_dn = config.getString(rm_prefix_str + "base_dn", "");
rm_params.search_filter = config.getString(rm_prefix_str + "search_filter", "");
rm_params.attribute = config.getString(rm_prefix_str + "attribute", "cn");
rm_params.fail_if_all_rules_mismatch = config.getBool(rm_prefix_str + "fail_if_all_rules_mismatch", true);
rm_params.prefix = config.getString(rm_prefix_str + "prefix", "");
auto scope = config.getString(rm_prefix_str + "scope", "subtree");
boost::algorithm::to_lower(scope);
@ -95,33 +94,6 @@ void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_m
else if (scope == "children") rm_params.scope = LDAPSearchParams::Scope::CHILDREN;
else
throw Exception("Invalid value of 'scope' field in '" + key + "' section of LDAP user directory, must be one of 'base', 'one_level', 'subtree', or 'children'", ErrorCodes::BAD_ARGUMENTS);
Poco::Util::AbstractConfiguration::Keys all_mapping_keys;
config.keys(rm_prefix, all_mapping_keys);
for (const auto & mkey : all_mapping_keys)
{
if (mkey != "rule" && mkey.find("rule[") != 0)
continue;
const String rule_prefix = rm_prefix_str + mkey;
const String rule_prefix_str = rule_prefix + '.';
rm_params.rules.emplace_back();
auto & rule = rm_params.rules.back();
rule.match = config.getString(rule_prefix_str + "match", ".+");
try
{
// Construct unused regex instance just to check the syntax.
std::regex(rule.match, std::regex_constants::ECMAScript);
}
catch (const std::regex_error & e)
{
throw Exception("ECMAScript regex syntax error in 'match' field in '" + mkey + "' rule of '" + key + "' section of LDAP user directory: " + e.what(), ErrorCodes::BAD_ARGUMENTS);
}
rule.replace = config.getString(rule_prefix_str + "replace", "$&");
rule.continue_on_match = config.getBool(rule_prefix_str + "continue_on_match", false);
}
}
}
@ -255,7 +227,7 @@ void LDAPAccessStorage::grantRolesNoLock(User & user, const LDAPSearchResultsLis
auto & granted_roles = user.granted_roles.roles;
// Map external role names to local role names.
const auto user_role_names = mapExternalRolesNoLock(user_name, external_roles);
const auto user_role_names = mapExternalRolesNoLock(external_roles);
external_role_hashes.erase(user_name);
granted_roles.clear();
@ -365,36 +337,27 @@ void LDAPAccessStorage::updateRolesNoLock(const UUID & id, const String & user_n
}
std::set<String> LDAPAccessStorage::mapExternalRolesNoLock(const String & user_name, const LDAPSearchResultsList & external_roles) const
std::set<String> LDAPAccessStorage::mapExternalRolesNoLock(const LDAPSearchResultsList & external_roles) const
{
std::set<String> role_names;
if (external_roles.size() != role_search_params.size())
throw Exception("Unable to match external roles to mapping rules", ErrorCodes::BAD_ARGUMENTS);
throw Exception("Unable to map external roles", ErrorCodes::BAD_ARGUMENTS);
std::unordered_map<String, std::regex> re_cache;
for (std::size_t i = 0; i < external_roles.size(); ++i)
{
const auto & external_role_set = external_roles[i];
const auto & rules = role_search_params[i].rules;
const auto & prefix = role_search_params[i].prefix;
for (const auto & external_role : external_role_set)
{
bool have_match = false;
for (const auto & rule : rules)
if (
prefix.size() < external_role.size() &&
external_role.compare(0, prefix.size(), prefix) == 0
)
{
const auto & re = re_cache.try_emplace(rule.match, rule.match, std::regex_constants::ECMAScript | std::regex_constants::optimize).first->second;
std::smatch match_results;
if (std::regex_match(external_role, match_results, re))
{
role_names.emplace(match_results.format(rule.replace));
have_match = true;
if (!rule.continue_on_match)
break;
}
role_names.emplace(external_role.substr(prefix.size()));
}
if (!have_match && role_search_params[i].fail_if_all_rules_mismatch)
throw Exception("None of the external role mapping rules were able to match '" + external_role + "' string, received from LDAP server '" + ldap_server + "' for user '" + user_name + "'", ErrorCodes::BAD_ARGUMENTS);
}
}
@ -436,7 +399,7 @@ String LDAPAccessStorage::getStorageParamsJSON() const
role_mapping_json.set("base_dn", role_mapping.base_dn);
role_mapping_json.set("search_filter", role_mapping.search_filter);
role_mapping_json.set("attribute", role_mapping.attribute);
role_mapping_json.set("fail_if_all_rules_mismatch", role_mapping.fail_if_all_rules_mismatch);
role_mapping_json.set("prefix", role_mapping.prefix);
String scope;
switch (role_mapping.scope)
@ -448,17 +411,6 @@ String LDAPAccessStorage::getStorageParamsJSON() const
}
role_mapping_json.set("scope", scope);
Poco::JSON::Array rules_json;
for (const auto & rule : role_mapping.rules)
{
Poco::JSON::Object rule_json;
rule_json.set("match", rule.match);
rule_json.set("replace", rule.replace);
rule_json.set("continue_on_match", rule.continue_on_match);
rules_json.add(rule_json);
}
role_mapping_json.set("rules", rules_json);
role_mappings_json.add(role_mapping_json);
}
params_json.set("role_mappings", role_mappings_json);

View File

@ -67,7 +67,7 @@ private:
void applyRoleChangeNoLock(bool grant, const UUID & role_id, const String & role_name);
void grantRolesNoLock(User & user, const LDAPSearchResultsList & external_roles) const;
void updateRolesNoLock(const UUID & id, const String & user_name, const LDAPSearchResultsList & external_roles) const;
std::set<String> mapExternalRolesNoLock(const String & user_name, const LDAPSearchResultsList & external_roles) const;
std::set<String> mapExternalRolesNoLock(const LDAPSearchResultsList & external_roles) const;
bool isPasswordCorrectLDAPNoLock(const User & user, const String & password, const ExternalAuthenticators & external_authenticators, LDAPSearchResultsList & search_results) const;
mutable std::recursive_mutex mutex;

View File

@ -10,14 +10,6 @@
namespace DB
{
struct LDAPRoleMappingRules
{
String match = ".+";
String replace = "$&";
bool continue_on_match = false;
};
struct LDAPSearchParams
{
enum class Scope
@ -32,9 +24,7 @@ struct LDAPSearchParams
String search_filter;
String attribute = "cn";
Scope scope = Scope::SUBTREE;
bool fail_if_all_rules_mismatch = false;
std::vector<LDAPRoleMappingRules> rules;
String prefix;
};
using LDAPSearchParamsList = std::vector<LDAPSearchParams>;