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 < ydb/core/util/address_classifier.h>
6
+ #include < queue>
5
7
#include " ldap_auth_provider.h"
6
8
#include " ldap_utils.h"
7
9
@@ -69,6 +71,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
69
71
TLdapAuthProvider (const NKikimrProto::TLdapAuthentication& settings)
70
72
: Settings(settings)
71
73
, FilterCreator(Settings)
74
+ , UrisCreator(Settings, Settings.GetPort() != 0 ? Settings.GetPort() : NKikimrLdap::GetPort(Settings.GetScheme()))
72
75
{
73
76
const TString& requestedGroupAttribute = Settings.GetRequestedGroupAttribute ();
74
77
RequestedAttributes[0 ] = const_cast <char *>(requestedGroupAttribute.empty () ? " memberOf" : requestedGroupAttribute.c_str ());
@@ -135,18 +138,30 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
135
138
}
136
139
LDAPMessage* entry = NKikimrLdap::FirstEntry (ld, searchUserResponse.SearchMessage );
137
140
BerElement* ber = nullptr ;
138
- std::vector<TString> groupsDn ;
141
+ std::vector<TString> directUserGroups ;
139
142
char * attribute = NKikimrLdap::FirstAttribute (ld, entry, &ber);
140
143
if (attribute != nullptr ) {
141
- groupsDn = NKikimrLdap::GetAllValuesOfAttribute (ld, entry, attribute);
144
+ directUserGroups = NKikimrLdap::GetAllValuesOfAttribute (ld, entry, attribute);
142
145
NKikimrLdap::MemFree (attribute);
143
146
}
144
147
if (ber) {
145
148
NKikimrLdap::BerFree (ber, 0 );
146
149
}
150
+ std::vector<TString> allUserGroups;
151
+ if (!directUserGroups.empty ()) {
152
+ // Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain
153
+ // We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server?
154
+ // If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory
155
+ // but it is known that there are groups and we are trying to do tree traversal
156
+ allUserGroups = TryToGetGroupsUseMatchingRuleInChain (ld, entry);
157
+ if (allUserGroups.empty ()) {
158
+ allUserGroups = std::move (directUserGroups);
159
+ GetNestedGroups (ld, &allUserGroups);
160
+ }
161
+ }
147
162
NKikimrLdap::MsgFree (entry);
148
163
NKikimrLdap::Unbind (ld);
149
- Send (ev->Sender , new TEvLdapAuthProvider::TEvEnrichGroupsResponse (request->Key , request->User , groupsDn ));
164
+ Send (ev->Sender , new TEvLdapAuthProvider::TEvEnrichGroupsResponse (request->Key , request->User , allUserGroups ));
150
165
}
151
166
152
167
TInitAndBindResponse InitAndBind (LDAP** ld, std::function<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) {
@@ -173,7 +188,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
173
188
result = NKikimrLdap::Bind (*ld, Settings.GetBindDn (), Settings.GetBindPassword ());
174
189
if (!NKikimrLdap::IsSuccess (result)) {
175
190
TEvLdapAuthProvider::TError error {
176
- .Message = " Could not perform initial LDAP bind for dn " + Settings.GetBindDn () + " on server " + UrisList + " \n "
191
+ .Message = " Could not perform initial LDAP bind for dn " + Settings.GetBindDn () + " on server " + UrisCreator. GetUris () + " \n "
177
192
+ NKikimrLdap::ErrorToString (result),
178
193
.Retryable = NKikimrLdap::IsRetryableError (result)
179
194
};
@@ -202,12 +217,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
202
217
}
203
218
}
204
219
205
- const ui32 port = Settings.GetPort () != 0 ? Settings.GetPort () : NKikimrLdap::GetPort (Settings.GetScheme ());
206
- UrisList = GetUris (port);
207
- result = NKikimrLdap::Init (ld, Settings.GetScheme (), UrisList, port);
220
+ result = NKikimrLdap::Init (ld, Settings.GetScheme (), UrisCreator.GetUris (), UrisCreator.GetConfiguredPort ());
208
221
if (!NKikimrLdap::IsSuccess (result)) {
209
222
return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE,
210
- {.Message = " Could not initialize LDAP connection for uris: " + UrisList + " . " + NKikimrLdap::LdapError (*ld),
223
+ {.Message = " Could not initialize LDAP connection for uris: " + UrisCreator. GetUris () + " . " + NKikimrLdap::LdapError (*ld),
211
224
.Retryable = false }}};
212
225
}
213
226
@@ -237,14 +250,14 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
237
250
char * dn = NKikimrLdap::GetDn (*request.Ld , request.Entry );
238
251
if (dn == nullptr ) {
239
252
return {{TEvLdapAuthProvider::EStatus::UNAUTHORIZED,
240
- {.Message = " Could not get dn for the first entry matching " + FilterCreator.GetFilter (request.Login ) + " on server " + UrisList + " \n "
253
+ {.Message = " Could not get dn for the first entry matching " + FilterCreator.GetFilter (request.Login ) + " on server " + UrisCreator. GetUris () + " \n "
241
254
+ NKikimrLdap::LdapError (*request.Ld ),
242
255
.Retryable = false }}};
243
256
}
244
257
TEvLdapAuthProvider::TError error;
245
258
int result = NKikimrLdap::Bind (*request.Ld , dn, request.Password );
246
259
if (!NKikimrLdap::IsSuccess (result)) {
247
- error.Message = " LDAP login failed for user " + TString (dn) + " on server " + UrisList + " \n "
260
+ error.Message = " LDAP login failed for user " + TString (dn) + " on server " + UrisCreator. GetUris () + " \n "
248
261
+ NKikimrLdap::ErrorToString ((result));
249
262
error.Retryable = NKikimrLdap::IsRetryableError (result);
250
263
}
@@ -266,7 +279,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
266
279
TSearchUserResponse response;
267
280
if (!NKikimrLdap::IsSuccess (result)) {
268
281
response.Status = NKikimrLdap::ErrorToStatus (result);
269
- response.Error = {.Message = " Could not search for filter " + searchFilter + " on server " + UrisList + " \n "
282
+ response.Error = {.Message = " Could not search for filter " + searchFilter + " on server " + UrisCreator. GetUris () + " \n "
270
283
+ NKikimrLdap::ErrorToString (result),
271
284
.Retryable = NKikimrLdap::IsRetryableError (result)};
272
285
return response;
@@ -275,11 +288,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
275
288
if (countEntries != 1 ) {
276
289
if (countEntries == 0 ) {
277
290
response.Error = {.Message = " LDAP user " + request.User + " does not exist. "
278
- " LDAP search for filter " + searchFilter + " on server " + UrisList + " return no entries" ,
291
+ " LDAP search for filter " + searchFilter + " on server " + UrisCreator. GetUris () + " return no entries" ,
279
292
.Retryable = false };
280
293
} else {
281
294
response.Error = {.Message = " LDAP user " + request.User + " is not unique. "
282
- " LDAP search for filter " + searchFilter + " on server " + UrisList + " return " + countEntries + " entries" ,
295
+ " LDAP search for filter " + searchFilter + " on server " + UrisCreator. GetUris () + " return " + countEntries + " entries" ,
283
296
.Retryable = false };
284
297
}
285
298
response.Status = TEvLdapAuthProvider::EStatus::UNAUTHORIZED;
@@ -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 }};
@@ -306,42 +392,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
306
392
return {TEvLdapAuthProvider::EStatus::SUCCESS, {}};
307
393
}
308
394
309
- TString GetUris (ui32 port) const {
310
- TStringBuilder uris;
311
- if (Settings.HostsSize () > 0 ) {
312
- for (const auto & host : Settings.GetHosts ()) {
313
- uris << CreateUri (host, port) << " " ;
314
- }
315
- uris.remove (uris.size () - 1 );
316
- } else {
317
- uris << CreateUri (Settings.GetHost (), port);
318
- }
319
- return uris;
320
- }
321
-
322
- TString CreateUri (const TString& endpoint, ui32 port) const {
323
- TStringBuilder uri;
324
- uri << Settings.GetScheme () << " ://" << endpoint;
325
- if (!HasEndpointPort (endpoint)) {
326
- uri << ' :' << port;
327
- }
328
- return uri;
329
- }
330
-
331
- static bool HasEndpointPort (const TString& endpoint) {
332
- size_t colonPos = endpoint.rfind (' :' );
333
- if (colonPos == TString::npos) {
334
- return false ;
335
- }
336
- ++colonPos;
337
- return (endpoint.size () - colonPos) > 0 ;
338
- }
339
-
340
395
private:
341
396
const NKikimrProto::TLdapAuthentication Settings;
342
397
const TSearchFilterCreator FilterCreator;
398
+ const TLdapUrisCreator UrisCreator;
343
399
char * RequestedAttributes[2 ];
344
- TString UrisList;
345
400
};
346
401
347
402
IActor* CreateLdapAuthProvider (const NKikimrProto::TLdapAuthentication& settings) {
0 commit comments