aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Molotkov <molotkov-and@ydb.tech>2024-07-18 19:27:12 +0300
committerGitHub <noreply@github.com>2024-07-18 19:27:12 +0300
commitd1fc6c4593eb353405e4c000f96d83441e04e194 (patch)
tree1f9d9f64b5af83212e87e08cde9d9a138d2a028f
parent6bb1a844163ee900ee36821a9ae355ea16f4a306 (diff)
downloadydb-d1fc6c4593eb353405e4c000f96d83441e04e194.tar.gz
[LDAP] Fetch nested groups for user (#6353)
-rw-r--r--ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp92
-rw-r--r--ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp4
-rw-r--r--ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp536
-rw-r--r--ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp4
-rw-r--r--ydb/core/security/ldap_auth_provider/ldap_compat.h1
-rw-r--r--ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp66
-rw-r--r--ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h11
-rw-r--r--ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp48
-rw-r--r--ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h2
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;