Skip to content

Commit d1fc6c4

Browse files
authored
[LDAP] Fetch nested groups for user (#6353)
1 parent 6bb1a84 commit d1fc6c4

File tree

9 files changed

+691
-73
lines changed

9 files changed

+691
-73
lines changed

ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <ydb/library/actors/core/log.h>
33
#include <ydb/core/base/ticket_parser.h>
44
#include <ydb/core/security/ticket_parser_log.h>
5+
#include <queue>
56
#include "ldap_auth_provider.h"
67
#include "ldap_utils.h"
78

@@ -135,18 +136,30 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
135136
}
136137
LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage);
137138
BerElement* ber = nullptr;
138-
std::vector<TString> groupsDn;
139+
std::vector<TString> directUserGroups;
139140
char* attribute = NKikimrLdap::FirstAttribute(ld, entry, &ber);
140141
if (attribute != nullptr) {
141-
groupsDn = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute);
142+
directUserGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute);
142143
NKikimrLdap::MemFree(attribute);
143144
}
144145
if (ber) {
145146
NKikimrLdap::BerFree(ber, 0);
146147
}
148+
std::vector<TString> allUserGroups;
149+
if (!directUserGroups.empty()) {
150+
// Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain
151+
// We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server?
152+
// If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory
153+
// but it is known that there are groups and we are trying to do tree traversal
154+
allUserGroups = TryToGetGroupsUseMatchingRuleInChain(ld, entry);
155+
if (allUserGroups.empty()) {
156+
allUserGroups = std::move(directUserGroups);
157+
GetNestedGroups(ld, &allUserGroups);
158+
}
159+
}
147160
NKikimrLdap::MsgFree(entry);
148161
NKikimrLdap::Unbind(ld);
149-
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, groupsDn));
162+
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, allUserGroups));
150163
}
151164

152165
TInitAndBindResponse InitAndBind(LDAP** ld, std::function<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) {
@@ -290,6 +303,79 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
290303
return response;
291304
}
292305

306+
std::vector<TString> TryToGetGroupsUseMatchingRuleInChain(LDAP* ld, LDAPMessage* entry) const {
307+
static const TString matchingRuleInChain = "1.2.840.113556.1.4.1941"; // Only Active Directory supports
308+
TStringBuilder filter;
309+
filter << "(member:" << matchingRuleInChain << ":=" << NKikimrLdap::GetDn(ld, entry) << ')';
310+
LDAPMessage* searchMessage = nullptr;
311+
int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, NKikimrLdap::noAttributes, 0, &searchMessage);
312+
if (!NKikimrLdap::IsSuccess(result)) {
313+
return {};
314+
}
315+
const int countEntries = NKikimrLdap::CountEntries(ld, searchMessage);
316+
if (countEntries == 0) {
317+
NKikimrLdap::MsgFree(searchMessage);
318+
return {};
319+
}
320+
std::vector<TString> groups;
321+
groups.reserve(countEntries);
322+
for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) {
323+
groups.push_back(NKikimrLdap::GetDn(ld, groupEntry));
324+
}
325+
NKikimrLdap::MsgFree(searchMessage);
326+
return groups;
327+
}
328+
329+
void GetNestedGroups(LDAP* ld, std::vector<TString>* groups) {
330+
std::unordered_set<TString> viewedGroups(groups->cbegin(), groups->cend());
331+
std::queue<TString> queue;
332+
for (const auto& group : *groups) {
333+
queue.push(group);
334+
}
335+
while (!queue.empty()) {
336+
TStringBuilder filter;
337+
filter << "(|";
338+
filter << "(entryDn=" << queue.front() << ')';
339+
queue.pop();
340+
//should filter string is separated into several batches
341+
while (!queue.empty()) {
342+
// entryDn specific for OpenLdap, may get this value from config
343+
filter << "(entryDn=" << queue.front() << ')';
344+
queue.pop();
345+
}
346+
filter << ')';
347+
LDAPMessage* searchMessage = nullptr;
348+
int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, RequestedAttributes, 0, &searchMessage);
349+
if (!NKikimrLdap::IsSuccess(result)) {
350+
return;
351+
}
352+
if (NKikimrLdap::CountEntries(ld, searchMessage) == 0) {
353+
NKikimrLdap::MsgFree(searchMessage);
354+
return;
355+
}
356+
for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) {
357+
BerElement* ber = nullptr;
358+
std::vector<TString> foundGroups;
359+
char* attribute = NKikimrLdap::FirstAttribute(ld, groupEntry, &ber);
360+
if (attribute != nullptr) {
361+
foundGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, groupEntry, attribute);
362+
NKikimrLdap::MemFree(attribute);
363+
}
364+
if (ber) {
365+
NKikimrLdap::BerFree(ber, 0);
366+
}
367+
for (const auto& newGroup : foundGroups) {
368+
if (!viewedGroups.contains(newGroup)) {
369+
viewedGroups.insert(newGroup);
370+
queue.push(newGroup);
371+
groups->push_back(newGroup);
372+
}
373+
}
374+
}
375+
NKikimrLdap::MsgFree(searchMessage);
376+
}
377+
}
378+
293379
TInitializeLdapConnectionResponse CheckRequiredSettingsParameters() const {
294380
if (Settings.GetHosts().empty() && Settings.GetHost().empty()) {
295381
return {TEvLdapAuthProvider::EStatus::UNAVAILABLE, {.Message = "List of ldap server hosts is empty", .Retryable = false}};

ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) {
7777
return ldap_first_entry(ld, chain);
7878
}
7979

80+
LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) {
81+
return ldap_next_entry(ld, entry);
82+
}
83+
8084
char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) {
8185
return ldap_first_attribute(ld, entry, berout);
8286
}

0 commit comments

Comments
 (0)