diff options
author | Andrey Molotkov <molotkov-and@ydb.tech> | 2024-07-18 19:27:12 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-18 19:27:12 +0300 |
commit | d1fc6c4593eb353405e4c000f96d83441e04e194 (patch) | |
tree | 1f9d9f64b5af83212e87e08cde9d9a138d2a028f | |
parent | 6bb1a844163ee900ee36821a9ae355ea16f4a306 (diff) | |
download | ydb-d1fc6c4593eb353405e4c000f96d83441e04e194.tar.gz |
[LDAP] Fetch nested groups for user (#6353)
9 files changed, 691 insertions, 73 deletions
diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp index a3ec33e9ad..2c616fa2b2 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp @@ -2,6 +2,7 @@ #include <ydb/library/actors/core/log.h> #include <ydb/core/base/ticket_parser.h> #include <ydb/core/security/ticket_parser_log.h> +#include <queue> #include "ldap_auth_provider.h" #include "ldap_utils.h" @@ -135,18 +136,30 @@ private: } LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage); BerElement* ber = nullptr; - std::vector<TString> groupsDn; + std::vector<TString> directUserGroups; char* attribute = NKikimrLdap::FirstAttribute(ld, entry, &ber); if (attribute != nullptr) { - groupsDn = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); + directUserGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); NKikimrLdap::MemFree(attribute); } if (ber) { NKikimrLdap::BerFree(ber, 0); } + std::vector<TString> allUserGroups; + if (!directUserGroups.empty()) { + // Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain + // We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server? + // If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory + // but it is known that there are groups and we are trying to do tree traversal + allUserGroups = TryToGetGroupsUseMatchingRuleInChain(ld, entry); + if (allUserGroups.empty()) { + allUserGroups = std::move(directUserGroups); + GetNestedGroups(ld, &allUserGroups); + } + } NKikimrLdap::MsgFree(entry); NKikimrLdap::Unbind(ld); - Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, groupsDn)); + Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, allUserGroups)); } TInitAndBindResponse InitAndBind(LDAP** ld, std::function<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) { @@ -290,6 +303,79 @@ private: return response; } + std::vector<TString> TryToGetGroupsUseMatchingRuleInChain(LDAP* ld, LDAPMessage* entry) const { + static const TString matchingRuleInChain = "1.2.840.113556.1.4.1941"; // Only Active Directory supports + TStringBuilder filter; + filter << "(member:" << matchingRuleInChain << ":=" << NKikimrLdap::GetDn(ld, entry) << ')'; + LDAPMessage* searchMessage = nullptr; + int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, NKikimrLdap::noAttributes, 0, &searchMessage); + if (!NKikimrLdap::IsSuccess(result)) { + return {}; + } + const int countEntries = NKikimrLdap::CountEntries(ld, searchMessage); + if (countEntries == 0) { + NKikimrLdap::MsgFree(searchMessage); + return {}; + } + std::vector<TString> groups; + groups.reserve(countEntries); + for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) { + groups.push_back(NKikimrLdap::GetDn(ld, groupEntry)); + } + NKikimrLdap::MsgFree(searchMessage); + return groups; + } + + void GetNestedGroups(LDAP* ld, std::vector<TString>* groups) { + std::unordered_set<TString> viewedGroups(groups->cbegin(), groups->cend()); + std::queue<TString> queue; + for (const auto& group : *groups) { + queue.push(group); + } + while (!queue.empty()) { + TStringBuilder filter; + filter << "(|"; + filter << "(entryDn=" << queue.front() << ')'; + queue.pop(); + //should filter string is separated into several batches + while (!queue.empty()) { + // entryDn specific for OpenLdap, may get this value from config + filter << "(entryDn=" << queue.front() << ')'; + queue.pop(); + } + filter << ')'; + LDAPMessage* searchMessage = nullptr; + int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, RequestedAttributes, 0, &searchMessage); + if (!NKikimrLdap::IsSuccess(result)) { + return; + } + if (NKikimrLdap::CountEntries(ld, searchMessage) == 0) { + NKikimrLdap::MsgFree(searchMessage); + return; + } + for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) { + BerElement* ber = nullptr; + std::vector<TString> foundGroups; + char* attribute = NKikimrLdap::FirstAttribute(ld, groupEntry, &ber); + if (attribute != nullptr) { + foundGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, groupEntry, attribute); + NKikimrLdap::MemFree(attribute); + } + if (ber) { + NKikimrLdap::BerFree(ber, 0); + } + for (const auto& newGroup : foundGroups) { + if (!viewedGroups.contains(newGroup)) { + viewedGroups.insert(newGroup); + queue.push(newGroup); + groups->push_back(newGroup); + } + } + } + NKikimrLdap::MsgFree(searchMessage); + } + } + TInitializeLdapConnectionResponse CheckRequiredSettingsParameters() const { if (Settings.GetHosts().empty() && Settings.GetHost().empty()) { return {TEvLdapAuthProvider::EStatus::UNAVAILABLE, {.Message = "List of ldap server hosts is empty", .Retryable = false}}; diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp index f7826f019a..4125182f3d 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp @@ -77,6 +77,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) { return ldap_first_entry(ld, chain); } +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) { + return ldap_next_entry(ld, entry); +} + char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) { return ldap_first_attribute(ld, entry, berout); } diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp index 5c5ac98a27..5135cdc253 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp @@ -184,23 +184,394 @@ TAutoPtr<IEventHandle> LdapAuthenticate(TLdapKikimrServer& server, const TString return handle; } +// Scheme of groups +// *-> cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | | +// | |*-> cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | | +// | |*-> uid=ldapuser,dc=search,dc=yandex,dc=net +// | +// |*-> cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> uid=ldapuser,dc=search,dc=yandex,dc=net + class TCorrectLdapResponse { public: - static std::vector<TString> Groups; + static std::vector<TString> DirectGroups; + static std::vector<TString> UpdatedDirectGroups; + static std::vector<TString> ManagerGroups; + static std::vector<TString> DevelopersGroups; + static std::vector<TString> PeopleGroups; static LdapMock::TLdapMockResponses GetResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetAdResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetUpdatedResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static THashSet<TString> GetAllGroups(const TString& domain) { + THashSet<TString> result; + auto AddGroups = [&result, &domain] (const std::vector<TString>& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(DirectGroups); + AddGroups(ManagerGroups); + AddGroups(DevelopersGroups); + AddGroups(PeopleGroups); + return result; + } + + static THashSet<TString> GetAllUpdatedGroups(const TString& domain) { + THashSet<TString> result; + auto AddGroups = [&result, &domain] (const std::vector<TString>& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(UpdatedDirectGroups); + AddGroups(DevelopersGroups); + AddGroups(PeopleGroups); + return result; + } +}; + +std::vector<TString> TCorrectLdapResponse::DirectGroups { + "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net" }; -std::vector<TString> TCorrectLdapResponse::Groups { - "ou=groups,dc=search,dc=yandex,dc=net", +std::vector<TString> TCorrectLdapResponse::UpdatedDirectGroups { + "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net" +}; + +std::vector<TString> TCorrectLdapResponse::ManagerGroups { + "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", +}; + +std::vector<TString> TCorrectLdapResponse::DevelopersGroups { + "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", +}; + +std::vector<TString> TCorrectLdapResponse::PeopleGroups { "cn=people,ou=groups,dc=search,dc=yandex,dc=net", - "cn=developers,ou=groups,dc=search,dc=yandex,dc=net" }; LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& login, const TString& groupAttribute) { LdapMock::TLdapMockResponses responses; responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); - LdapMock::TSearchRequestInfo fetchGroupsSearchRequestInfo { + LdapMock::TSearchRequestInfo requestDirectedUserGroups { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseDirectedUserGroupsEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DirectGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupOfManagers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupOfManagers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfManagers->Attribute = "entryDn"; + filterToGetGroupOfManagers->Value = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupOfDevelopers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupOfDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfDevelopers->Attribute = "entryDn"; + filterToGetGroupOfDevelopers->Value = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetGroupsOfManagersAndDevelopers = { + filterToGetGroupOfManagers, + filterToGetGroupOfDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupsOfManagersAndDevelopers { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupsOfManagersAndDevelopers}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseEntriesWithGroupsOfManagersAndDevelopers { + { + .Dn = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::ManagerGroups} + } + }, + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DevelopersGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupsOfManagersAndDevelopers { + .ResponseEntries = responseEntriesWithGroupsOfManagersAndDevelopers, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupsOfManagersAndDevelopers, responseWithGroupsOfManagersAndDevelopers}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupPeopleFromManagers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupPeopleFromManagers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromManagers->Attribute = "entryDn"; + filterToGetGroupPeopleFromManagers->Value = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupPeopleFromDevelopers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupPeopleFromDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromDevelopers->Attribute = "entryDn"; + filterToGetGroupPeopleFromDevelopers->Value = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetGroupOfPeople = { + filterToGetGroupPeopleFromManagers, filterToGetGroupPeopleFromDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseWithGroupOfPeopleEntries { + { + .Dn = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupOfPeople { + .ResponseEntries = responseWithGroupOfPeopleEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupOfPeople, responseWithGroupOfPeople}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetParentGroupOfPeople = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetParentGroupOfPeople->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetParentGroupOfPeople->Attribute = "entryDn"; + filterToGetParentGroupOfPeople->Value = "cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetParentGroupOfPeople = { + filterToGetParentGroupOfPeople + }; + LdapMock::TSearchRequestInfo requestToGetParentGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetParentGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + LdapMock::TSearchResponseInfo responseWithParentGroupOfPeople { + .ResponseEntries = {}, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetParentGroupOfPeople, responseWithParentGroupOfPeople}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} + } + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = {}, // LDAP server is not Active Directory. Return empty entries + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + + return responses; +} + +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TString& login, const TString& groupAttribute) { + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo requestDirectedUserGroups { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseDirectedUserGroupsEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, UpdatedDirectGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupOfDevelopers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupOfDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfDevelopers->Attribute = "entryDn"; + filterToGetGroupOfDevelopers->Value = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetGroupsOfDevelopers = { + filterToGetGroupOfDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupsOfDevelopers { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupsOfDevelopers}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseEntriesWithGroupsOfDevelopers { + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DevelopersGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupsOfDevelopers { + .ResponseEntries = responseEntriesWithGroupsOfDevelopers, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupsOfDevelopers, responseWithGroupsOfDevelopers}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetGroupPeopleFromDevelopers = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetGroupPeopleFromDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromDevelopers->Attribute = "entryDn"; + filterToGetGroupPeopleFromDevelopers->Value = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetGroupOfPeople = { + filterToGetGroupPeopleFromDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + std::vector<LdapMock::TSearchEntry> responseWithGroupOfPeopleEntries { + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupOfPeople { + .ResponseEntries = responseWithGroupOfPeopleEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupOfPeople, responseWithGroupOfPeople}); + + std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter> filterToGetParentGroupOfPeople = std::make_shared<LdapMock::TSearchRequestInfo::TSearchFilter>(); + filterToGetParentGroupOfPeople->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetParentGroupOfPeople->Attribute = "entryDn"; + filterToGetParentGroupOfPeople->Value = "cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector<std::shared_ptr<LdapMock::TSearchRequestInfo::TSearchFilter>> nestedFiltersToGetParentGroupOfPeople = { + filterToGetParentGroupOfPeople + }; + LdapMock::TSearchRequestInfo requestToGetParentGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetParentGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + LdapMock::TSearchResponseInfo responseWithParentGroupOfPeople { + .ResponseEntries = {}, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetParentGroupOfPeople, responseWithParentGroupOfPeople}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} + } + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = {}, // LDAP server is not Active Directory. Return empty entries + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + + return responses; +} + +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetAdResponses(const TString& login, const TString& groupAttribute) { + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo requestDirectedUserGroups { { .BaseDn = "dc=search,dc=yandex,dc=net", .Scope = 2, @@ -210,20 +581,65 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l } }; - std::vector<LdapMock::TSearchEntry> fetchGroupsSearchResponseEntries { + std::vector<LdapMock::TSearchEntry> responseDirectedUserGroupsEntries { { .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", .AttributeList = { - {groupAttribute, TCorrectLdapResponse::Groups} + {groupAttribute, TCorrectLdapResponse::DirectGroups} } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} } }; - LdapMock::TSearchResponseInfo fetchGroupsSearchResponseInfo { - .ResponseEntries = fetchGroupsSearchResponseEntries, + std::vector<LdapMock::TSearchEntry> responseWithAllNestedGroupsFromAdEntries { + { + .Dn = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = responseWithAllNestedGroupsFromAdEntries, .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} }; - responses.SearchResponses.push_back({fetchGroupsSearchRequestInfo, fetchGroupsSearchResponseInfo}); + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + return responses; } @@ -251,7 +667,7 @@ void CheckRequiredLdapSettings(std::function<void(NKikimrProto::TLdapAuthenticat Y_UNIT_TEST_SUITE(LdapAuthProviderTest) { -void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& secureType) { + void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -267,10 +683,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet<TString> expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -293,6 +706,45 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); } + void LdapFetchGroupsFromAdLdapServer(const ESecurityConnectionType& secureType) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + TLdapKikimrServer server(InitLdapSettings, secureType); + LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), TCorrectLdapResponse::GetAdResponses(login), secureType == ESecurityConnectionType::LDAPS_SCHEME); + + TAutoPtr<IEventHandle> handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get<TEvTicketParser::TEvAuthorizeTicketResult>(); + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + const TString ldapDomain = "@ldap"; + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain); + const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); + expectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); + for (const auto& expectedGroup : expectedGroups) { + UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer_nonSecure) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer_StartTls) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer_LdapsScheme) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::LDAPS_SCHEME); + } + void LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -309,10 +761,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet<TString> expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -351,10 +800,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet<TString> expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -615,36 +1061,8 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& auto responses = TCorrectLdapResponse::GetResponses(login); - LdapMock::TLdapMockResponses updatedResponses = responses; - - std::vector<TString> newLdapGroups { - "ou=groups,dc=search,dc=yandex,dc=net", - "cn=people,ou=groups,dc=search,dc=yandex,dc=net", - "cn=designers,ou=groups,dc=search,dc=yandex,dc=net" - }; - std::vector<LdapMock::TSearchEntry> newFetchGroupsSearchResponseEntries { - { - .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", - .AttributeList = { - {"memberOf", newLdapGroups} - } - } - }; - + LdapMock::TLdapMockResponses updatedResponses = TCorrectLdapResponse::GetUpdatedResponses(login); const TString ldapDomain = "@ldap"; - THashSet<TString> newExpectedGroups; - std::transform(newLdapGroups.begin(), newLdapGroups.end(), std::inserter(newExpectedGroups, newExpectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); - newExpectedGroups.insert("all-users@well-known"); - - LdapMock::TSearchResponseInfo newFetchGroupsSearchResponseInfo { - .ResponseEntries = newFetchGroupsSearchResponseEntries, - .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} - }; - - auto& searchResponse = updatedResponses.SearchResponses.front(); - searchResponse.second = newFetchGroupsSearchResponseInfo; TLdapKikimrServer server(InitLdapSettings, secureType); LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), {responses, updatedResponses}, secureType == ESecurityConnectionType::LDAPS_SCHEME); @@ -662,10 +1080,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet<TString> expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -684,6 +1099,10 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + "@ldap"); const auto& newFetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> newGroups(newFetchedGroups.begin(), newFetchedGroups.end()); + + THashSet<TString> newExpectedGroups = TCorrectLdapResponse::GetAllUpdatedGroups(ldapDomain); + newExpectedGroups.insert("all-users@well-known"); + UNIT_ASSERT_VALUES_EQUAL(newFetchedGroups.size(), newExpectedGroups.size()); for (const auto& expectedGroup : newExpectedGroups) { UNIT_ASSERT_C(newGroups.contains(expectedGroup), "Can not find " + expectedGroup); @@ -734,10 +1153,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet<TString> expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp index f1fc86e585..d1fc38a449 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp @@ -66,6 +66,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) { return ldap_first_entry(ld, chain); } +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) { + return ldap_next_entry(ld, entry); +} + char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) { return ldap_first_attribute(ld, entry, berout); } diff --git a/ydb/core/security/ldap_auth_provider/ldap_compat.h b/ydb/core/security/ldap_auth_provider/ldap_compat.h index 15aee21e9c..48e04441f0 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_compat.h +++ b/ydb/core/security/ldap_auth_provider/ldap_compat.h @@ -37,6 +37,7 @@ int Search(LDAP* ld, TString LdapError(LDAP* ld); TString ErrorToString(int err); LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain); +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry); char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout); void MemFree(char* p); void BerFree(BerElement* ber, int freebuf); diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp index 06170b7a1c..48335443fb 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp @@ -1,7 +1,65 @@ +#include <queue> #include "ldap_defines.h" namespace LdapMock { +namespace { + +bool checkFilters(const TSearchRequestInfo::TSearchFilter& filter1, const TSearchRequestInfo::TSearchFilter& filter2) { + if (filter1.Type != filter2.Type) { + return false; + } + if (filter1.Attribute != filter2.Attribute) { + return false; + } + if (filter1.Value != filter2.Value) { + return false; + } + if (filter1.Type == EFilterType::LDAP_FILTER_EXT) { + if (filter1.MatchingRule != filter2.MatchingRule) { + return false; + } + if (filter1.DnAttributes != filter2.DnAttributes) { + return false; + } + } + if (filter1.NestedFilters.size() != filter2.NestedFilters.size()) { + return false; + } + return true; +} + +bool AreFiltersEqual(const TSearchRequestInfo::TSearchFilter& filter1, const TSearchRequestInfo::TSearchFilter& filter2) { + if (!checkFilters(filter1, filter2)) { + return false; + } + std::queue<std::shared_ptr<TSearchRequestInfo::TSearchFilter>> q1; + for (const auto& filter : filter1.NestedFilters) { + q1.push(filter); + } + std::queue<std::shared_ptr<TSearchRequestInfo::TSearchFilter>> q2; + for (const auto& filter : filter2.NestedFilters) { + q2.push(filter); + } + while (!q1.empty() && !q2.empty()) { + const auto filterQ1 = q1.front(); + q1.pop(); + const auto filterQ2 = q2.front(); + q2.pop(); + if (!checkFilters(*filterQ1, *filterQ2)) { + return false; + } + for (const auto& filter : filterQ1->NestedFilters) { + q1.push(filter); + } + for (const auto& filter : filterQ2->NestedFilters) { + q2.push(filter); + } + } + return true; +} +} // namespace + TBindRequestInfo::TBindRequestInfo(const TString& login, const TString& password) : Login(login) , Password(password) @@ -53,13 +111,7 @@ bool TSearchRequestInfo::operator==(const TSearchRequestInfo& otherRequest) cons } const auto& filter = this->Filter; const auto& expectedFilter = otherRequest.Filter; - if (filter.Type != expectedFilter.Type) { - return false; - } - if (filter.Attribute != expectedFilter.Attribute) { - return false; - } - if (filter.Value != expectedFilter.Value) { + if (!AreFiltersEqual(filter, expectedFilter)) { return false; } if (this->Attributes != otherRequest.Attributes) { diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h index 4c8e14548c..ff52696a8b 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h @@ -39,6 +39,13 @@ enum EFilterType { LDAP_FILTER_EXT = 0xA9, }; +enum EExtendedFilterType { + LDAP_FILTER_EXT_OID = 0x81U, + LDAP_FILTER_EXT_TYPE = 0x82U, + LDAP_FILTER_EXT_VALUE = 0x83U, + LDAP_FILTER_EXT_DNATTRS = 0x84U, +}; + enum EElementType { BOOL = 0x01, STRING = 0x04, @@ -69,6 +76,10 @@ struct TSearchRequestInfo { EFilterType Type; TString Attribute; TString Value; + TString MatchingRule; + bool DnAttributes = false; + + std::vector<std::shared_ptr<TSearchFilter>> NestedFilters; }; struct TInitializeList { diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp index 8110e977f0..5e3069ce93 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp @@ -133,7 +133,6 @@ int TLdapRequestProcessor::ExtractMessageId() { std::vector<TLdapRequestProcessor::TProtocolOpData> TLdapRequestProcessor::Process(const TLdapMockResponses& responses) { unsigned char protocolOp = GetByte(); - switch (protocolOp) { case EProtocolOp::BIND_OP_REQUEST: { return ProcessBindRequest(responses.BindResponses); @@ -348,7 +347,8 @@ TSearchRequestInfo::TSearchFilter TLdapRequestProcessor::ProcessFilter() { return filter; } case EFilterType::LDAP_FILTER_OR: { - FillFilter(EFilterType::LDAP_FILTER_OR, "or"); + filter.Type = EFilterType::LDAP_FILTER_OR; + ProcessFilterOr(&filter, filterLength); return filter; } case EFilterType::LDAP_FILTER_NOT: { @@ -381,7 +381,8 @@ TSearchRequestInfo::TSearchFilter TLdapRequestProcessor::ProcessFilter() { return filter; } case EFilterType::LDAP_FILTER_EXT: { - FillFilter(EFilterType::LDAP_FILTER_EXT, "ext"); + filter.Type = EFilterType::LDAP_FILTER_EXT; + ProcessFilterExtensibleMatch(&filter, filterLength); return filter; } } @@ -401,4 +402,45 @@ void TLdapRequestProcessor::ProcessFilterEquality(TSearchRequestInfo::TSearchFil } } +void TLdapRequestProcessor::ProcessFilterExtensibleMatch(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter) { + const size_t limit = ReadBytes + lengthFilter; + size_t lastCheckedField = 0; + unsigned char elementType = GetByte(); + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_OID) { + filter->MatchingRule = GetString(); + lastCheckedField = 1; + } + + if (lastCheckedField == 1 && ReadBytes < limit) { + elementType = GetByte(); + } + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_TYPE) { + filter->Attribute = GetString(); + lastCheckedField = 2; + } + + if (lastCheckedField == 2 && ReadBytes < limit) { + elementType = GetByte(); + } + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_VALUE) { + filter->Value = GetString(); + } + + if (ReadBytes < limit) { + elementType = GetByte(); + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_DNATTRS) { + size_t length = GetLength(); + Y_UNUSED(length); + filter->DnAttributes = GetByte(); + } + } +} + +void TLdapRequestProcessor::ProcessFilterOr(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter) { + const size_t limit = ReadBytes + lengthFilter; + while (ReadBytes < limit) { + filter->NestedFilters.push_back(std::make_shared<TSearchRequestInfo::TSearchFilter>(ProcessFilter())); + } +} + } diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h index b41607b2e4..e39d9845f3 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h @@ -30,6 +30,8 @@ private: std::vector<TProtocolOpData> ProcessExtendedRequest(); TSearchRequestInfo::TSearchFilter ProcessFilter(); void ProcessFilterEquality(TSearchRequestInfo::TSearchFilter* filter); + void ProcessFilterExtensibleMatch(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter); + void ProcessFilterOr(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter); private: TAtomicSharedPtr<TLdapSocketWrapper> Socket; |