|
2 | 2 | #include <ydb/library/actors/core/log.h>
|
3 | 3 | #include <ydb/core/base/ticket_parser.h>
|
4 | 4 | #include <ydb/core/security/ticket_parser_log.h>
|
| 5 | +#include <queue> |
5 | 6 | #include "ldap_auth_provider.h"
|
6 | 7 | #include "ldap_utils.h"
|
7 | 8 |
|
@@ -135,18 +136,30 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
|
135 | 136 | }
|
136 | 137 | LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage);
|
137 | 138 | BerElement* ber = nullptr;
|
138 |
| - std::vector<TString> groupsDn; |
| 139 | + std::vector<TString> directUserGroups; |
139 | 140 | char* attribute = NKikimrLdap::FirstAttribute(ld, entry, &ber);
|
140 | 141 | if (attribute != nullptr) {
|
141 |
| - groupsDn = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); |
| 142 | + directUserGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); |
142 | 143 | NKikimrLdap::MemFree(attribute);
|
143 | 144 | }
|
144 | 145 | if (ber) {
|
145 | 146 | NKikimrLdap::BerFree(ber, 0);
|
146 | 147 | }
|
| 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 | + } |
147 | 160 | NKikimrLdap::MsgFree(entry);
|
148 | 161 | 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)); |
150 | 163 | }
|
151 | 164 |
|
152 | 165 | 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>
|
290 | 303 | return response;
|
291 | 304 | }
|
292 | 305 |
|
| 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 | + |
293 | 379 | TInitializeLdapConnectionResponse CheckRequiredSettingsParameters() const {
|
294 | 380 | if (Settings.GetHosts().empty() && Settings.GetHost().empty()) {
|
295 | 381 | return {TEvLdapAuthProvider::EStatus::UNAVAILABLE, {.Message = "List of ldap server hosts is empty", .Retryable = false}};
|
|
0 commit comments