Revisit mapped role management

This commit is contained in:
Denis Glazachev 2020-12-28 00:54:24 +04:00
parent d53919d832
commit 0c7b151800
2 changed files with 122 additions and 120 deletions

View File

@ -102,10 +102,11 @@ void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_m
role_search_params.swap(role_search_params_cfg);
common_role_names.swap(common_roles_cfg);
external_role_hashes.clear();
users_per_roles.clear();
roles_per_users.clear();
granted_role_names.clear();
granted_role_ids.clear();
external_role_hashes.clear();
role_change_subscription = access_control_manager->subscribeForChanges<Role>(
[this] (const UUID & id, const AccessEntityPtr & entity)
@ -113,46 +114,37 @@ void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_m
return this->processRoleChange(id, entity);
}
);
// Update granted_role_* with the initial values: resolved ids of roles from common_role_names.
for (const auto & role_name : common_role_names)
{
if (const auto role_id = access_control_manager->find<Role>(role_name))
{
granted_role_names.insert_or_assign(*role_id, role_name);
granted_role_ids.insert_or_assign(role_name, *role_id);
}
}
}
void LDAPAccessStorage::processRoleChange(const UUID & id, const AccessEntityPtr & entity)
{
std::scoped_lock lock(mutex);
auto role = typeid_cast<std::shared_ptr<const Role>>(entity);
const auto role = typeid_cast<std::shared_ptr<const Role>>(entity);
const auto it = granted_role_names.find(id);
if (role) // Added or renamed role.
if (role) // Added or renamed a role.
{
const auto & new_role_name = role->getName();
if (it != granted_role_names.end())
if (it != granted_role_names.end()) // Renamed a granted role.
{
// Revoke the old role if its name has been changed.
const auto & old_role_name = it->second;
if (new_role_name != old_role_name)
{
// Revoke the old role first, then grant the new role.
applyRoleChangeNoLock(false /* revoke */, id, old_role_name);
applyRoleChangeNoLock(true /* grant */, id, new_role_name);
}
}
// Grant the role.
applyRoleChangeNoLock(true /* grant */, id, new_role_name);
}
else // Removed role.
{
if (it != granted_role_names.end())
else // Added a role.
{
applyRoleChangeNoLock(true /* grant */, id, new_role_name);
}
}
else // Removed a role.
{
if (it != granted_role_names.end()) // Removed a granted role.
{
// Revoke the old role.
const auto & old_role_name = it->second;
applyRoleChangeNoLock(false /* revoke */, id, old_role_name);
}
@ -164,7 +156,7 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id,
{
std::vector<UUID> user_ids;
// Find relevant user ids.
// Build a list of ids of the relevant users.
if (common_role_names.count(role_name))
{
user_ids = memory_storage.findAll<User>();
@ -176,6 +168,7 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id,
{
const auto & user_names = it->second;
user_ids.reserve(user_names.size());
for (const auto & user_name : user_names)
{
if (const auto user_id = memory_storage.find<User>(user_name))
@ -184,7 +177,7 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id,
}
}
// Update relevant users' granted roles.
// Update the granted roles of the relevant users.
if (!user_ids.empty())
{
auto update_func = [&role_id, &grant] (const AccessEntityPtr & entity_) -> AccessEntityPtr
@ -205,129 +198,135 @@ void LDAPAccessStorage::applyRoleChangeNoLock(bool grant, const UUID & role_id,
};
memory_storage.update(user_ids, update_func);
}
if (grant)
// Actualize granted_role_* mappings.
if (grant)
{
if (!user_ids.empty())
{
granted_role_names.insert_or_assign(role_id, role_name);
granted_role_ids.insert_or_assign(role_name, role_id);
}
else
{
granted_role_names.erase(role_id);
granted_role_ids.erase(role_name);
}
}
else
{
granted_role_names.erase(role_id);
granted_role_ids.erase(role_name);
}
}
void LDAPAccessStorage::grantRolesNoLock(User & user, const LDAPSearchResultsList & external_roles) const
void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPSearchResultsList & external_roles) const
{
const auto external_roles_hash = boost::hash<LDAPSearchResultsList>{}(external_roles);
return assignRolesNoLock(user, external_roles, external_roles_hash);
}
void LDAPAccessStorage::assignRolesNoLock(User & user, const LDAPSearchResultsList & external_roles, const std::size_t external_roles_hash) const
{
const auto & user_name = user.getName();
const auto new_hash = boost::hash<LDAPSearchResultsList>{}(external_roles);
auto & granted_roles = user.granted_roles.roles;
const auto local_role_names = mapExternalRolesNoLock(external_roles);
// Map external role names to local role names.
const auto user_role_names = mapExternalRolesNoLock(external_roles);
external_role_hashes.erase(user_name);
granted_roles.clear();
// Grant the common roles.
// Initially, all the available ids of common roles were resolved in setConfiguration(),
// and, then, maintained by processRoleChange(), so here we just grant those that exist (i.e., resolved).
for (const auto & role_name : common_role_names)
auto grant_role = [this, &user_name, &granted_roles] (const String & role_name, const bool common)
{
const auto it = granted_role_ids.find(role_name);
if (it == granted_role_ids.end())
{
LOG_WARNING(getLogger(), "Unable to grant common role '{}' to user '{}': role not found", role_name, user_name);
}
else
{
const auto & role_id = it->second;
granted_roles.insert(role_id);
}
}
// Grant the mapped external roles.
// Cleanup helper relations.
for (auto it = users_per_roles.begin(); it != users_per_roles.end();)
{
const auto & role_name = it->first;
auto & user_names = it->second;
if (user_role_names.count(role_name) == 0)
{
user_names.erase(user_name);
if (user_names.empty())
{
if (common_role_names.count(role_name) == 0)
{
auto rit = granted_role_ids.find(role_name);
if (rit != granted_role_ids.end())
{
granted_role_names.erase(rit->second);
granted_role_ids.erase(rit);
}
}
users_per_roles.erase(it++);
}
else
{
++it;
}
}
else
{
++it;
}
}
// Resolve and assign mapped external role ids.
for (const auto & role_name : user_role_names)
{
users_per_roles[role_name].insert(user_name);
const auto it = granted_role_ids.find(role_name);
auto it = granted_role_ids.find(role_name);
if (it == granted_role_ids.end())
{
if (const auto role_id = access_control_manager->find<Role>(role_name))
{
granted_roles.insert(*role_id);
granted_role_names.insert_or_assign(*role_id, role_name);
granted_role_ids.insert_or_assign(role_name, *role_id);
}
else
{
LOG_WARNING(getLogger(), "Unable to grant mapped role '{}' to user '{}': role not found", role_name, user_name);
it = granted_role_ids.insert_or_assign(role_name, *role_id).first;
}
}
else
if (it != granted_role_ids.end())
{
const auto & role_id = it->second;
granted_roles.insert(role_id);
}
else
{
LOG_WARNING(getLogger(), "Unable to grant {} role '{}' to user '{}': role not found", (common ? "common" : "mapped"), role_name, user_name);
}
};
external_role_hashes.erase(user_name);
granted_roles.clear();
const auto old_role_names = std::move(roles_per_users[user_name]);
// Grant the common roles first.
for (const auto & role_name : common_role_names)
{
grant_role(role_name, true /* common */);
}
external_role_hashes[user_name] = new_hash;
// Grant the mapped external roles and actualize users_per_roles mapping.
// local_role_names allowed to overlap with common_role_names.
for (const auto & role_name : local_role_names)
{
grant_role(role_name, false /* mapped */);
users_per_roles[role_name].insert(user_name);
}
// Cleanup users_per_roles and granted_role_* mappings.
for (const auto & old_role_name : old_role_names)
{
if (local_role_names.count(old_role_name))
continue;
const auto rit = users_per_roles.find(old_role_name);
if (rit == users_per_roles.end())
continue;
auto & user_names = rit->second;
user_names.erase(user_name);
if (!user_names.empty())
continue;
users_per_roles.erase(rit);
if (common_role_names.count(old_role_name))
continue;
const auto iit = granted_role_ids.find(old_role_name);
if (iit == granted_role_ids.end())
continue;
const auto old_role_id = iit->second;
granted_role_names.erase(old_role_id);
granted_role_ids.erase(iit);
}
// Actualize roles_per_users mapping and external_role_hashes cache.
if (local_role_names.empty())
roles_per_users.erase(user_name);
else
roles_per_users[user_name] = std::move(local_role_names);
external_role_hashes[user_name] = external_roles_hash;
}
void LDAPAccessStorage::updateRolesNoLock(const UUID & id, const String & user_name, const LDAPSearchResultsList & external_roles) const
void LDAPAccessStorage::updateAssignedRolesNoLock(const UUID & id, const String & user_name, const LDAPSearchResultsList & external_roles) const
{
// common_role_names are not included since they don't change.
const auto new_hash = boost::hash<LDAPSearchResultsList>{}(external_roles);
// No need to include common_role_names in this hash each time, since they don't change.
const auto external_roles_hash = boost::hash<LDAPSearchResultsList>{}(external_roles);
// Map and grant the roles from scratch only if the list of external role has changed.
const auto it = external_role_hashes.find(user_name);
if (it != external_role_hashes.end() && it->second == new_hash)
if (it != external_role_hashes.end() && it->second == external_roles_hash)
return;
auto update_func = [this, &external_roles] (const AccessEntityPtr & entity_) -> AccessEntityPtr
auto update_func = [this, &external_roles, external_roles_hash] (const AccessEntityPtr & entity_) -> AccessEntityPtr
{
if (auto user = typeid_cast<std::shared_ptr<const User>>(entity_))
{
auto changed_user = typeid_cast<std::shared_ptr<User>>(user->clone());
grantRolesNoLock(*changed_user, external_roles);
assignRolesNoLock(*changed_user, external_roles, external_roles_hash);
return changed_user;
}
return entity_;
@ -529,7 +528,7 @@ UUID LDAPAccessStorage::loginImpl(const String & user_name, const String & passw
throwAddressNotAllowed(address);
// Just in case external_roles are changed. This will be no-op if they are not.
updateRolesNoLock(*id, user_name, external_roles);
updateAssignedRolesNoLock(*id, user_name, external_roles);
return *id;
}
@ -547,7 +546,7 @@ UUID LDAPAccessStorage::loginImpl(const String & user_name, const String & passw
if (!isAddressAllowedImpl(*user, address))
throwAddressNotAllowed(address);
grantRolesNoLock(*user, external_roles);
assignRolesNoLock(*user, external_roles);
return memory_storage.insert(user);
}
@ -570,9 +569,10 @@ UUID LDAPAccessStorage::getIDOfLoggedUserImpl(const String & user_name) const
user->authentication.setServerName(ldap_server);
LDAPSearchResultsList external_roles;
// TODO: mapped external roles are not available here. Implement?
grantRolesNoLock(*user, external_roles);
// TODO: mapped external roles are not available here. Without a password we can't authenticate and retrieve roles from LDAP server.
assignRolesNoLock(*user, external_roles);
return memory_storage.insert(user);
}

View File

@ -65,8 +65,9 @@ private:
void processRoleChange(const UUID & id, const AccessEntityPtr & entity);
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;
void assignRolesNoLock(User & user, const LDAPSearchResultsList & external_roles) const;
void assignRolesNoLock(User & user, const LDAPSearchResultsList & external_roles, const std::size_t external_roles_hash) const;
void updateAssignedRolesNoLock(const UUID & id, 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;
@ -74,11 +75,12 @@ private:
AccessControlManager * access_control_manager = nullptr;
String ldap_server;
LDAPSearchParamsList role_search_params;
std::set<String> common_role_names;
mutable std::map<String, std::set<String>> users_per_roles; // per-user roles: role name -> user names
mutable std::map<UUID, String> granted_role_names; // currently granted roles: role id -> role name
mutable std::map<String, UUID> granted_role_ids; // currently granted roles: role name -> role id
mutable std::map<String, std::size_t> external_role_hashes; // user name -> LDAPSearchResultsList hash
std::set<String> common_role_names; // role name that should be granted to all users at all times
mutable std::map<String, std::size_t> external_role_hashes; // user name -> LDAPSearchResultsList hash (most recently retrieved and processed)
mutable std::map<String, std::set<String>> users_per_roles; // role name -> user names (...it should be granted to; may but don't have to exist for common roles)
mutable std::map<String, std::set<String>> roles_per_users; // user name -> role names (...that should be granted to it; may but don't have to include common roles)
mutable std::map<UUID, String> granted_role_names; // (currently granted) role id -> its name
mutable std::map<String, UUID> granted_role_ids; // (currently granted) role name -> its id
ext::scope_guard role_change_subscription;
mutable MemoryAccessStorage memory_storage;
};