Passed
Push — master ( 34c9b5...835e28 )
by Blizzz
15:25 queued 23s
created
apps/user_ldap/lib/Group_LDAP.php 1 patch
Indentation   +1317 added lines, -1317 removed lines patch added patch discarded remove patch
@@ -53,1321 +53,1321 @@
 block discarded – undo
53 53
 use Psr\Log\LoggerInterface;
54 54
 
55 55
 class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend, IDeleteGroupBackend {
56
-	protected $enabled = false;
57
-
58
-	/** @var string[][] $cachedGroupMembers array of users with gid as key */
59
-	protected $cachedGroupMembers;
60
-	/** @var string[] $cachedGroupsByMember array of groups with uid as key */
61
-	protected $cachedGroupsByMember;
62
-	/** @var string[] $cachedNestedGroups array of groups with gid (DN) as key */
63
-	protected $cachedNestedGroups;
64
-	/** @var GroupPluginManager */
65
-	protected $groupPluginManager;
66
-	/** @var LoggerInterface */
67
-	protected $logger;
68
-
69
-	/**
70
-	 * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
71
-	 */
72
-	protected $ldapGroupMemberAssocAttr;
73
-
74
-	public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
75
-		parent::__construct($access);
76
-		$filter = $this->access->connection->ldapGroupFilter;
77
-		$gAssoc = $this->access->connection->ldapGroupMemberAssocAttr;
78
-		if (!empty($filter) && !empty($gAssoc)) {
79
-			$this->enabled = true;
80
-		}
81
-
82
-		$this->cachedGroupMembers = new CappedMemoryCache();
83
-		$this->cachedGroupsByMember = new CappedMemoryCache();
84
-		$this->cachedNestedGroups = new CappedMemoryCache();
85
-		$this->groupPluginManager = $groupPluginManager;
86
-		$this->logger = OC::$server->get(LoggerInterface::class);
87
-		$this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc);
88
-	}
89
-
90
-	/**
91
-	 * is user in group?
92
-	 *
93
-	 * @param string $uid uid of the user
94
-	 * @param string $gid gid of the group
95
-	 * @return bool
96
-	 * @throws Exception
97
-	 * @throws ServerNotAvailableException
98
-	 */
99
-	public function inGroup($uid, $gid) {
100
-		if (!$this->enabled) {
101
-			return false;
102
-		}
103
-		$cacheKey = 'inGroup' . $uid . ':' . $gid;
104
-		$inGroup = $this->access->connection->getFromCache($cacheKey);
105
-		if (!is_null($inGroup)) {
106
-			return (bool)$inGroup;
107
-		}
108
-
109
-		$userDN = $this->access->username2dn($uid);
110
-
111
-		if (isset($this->cachedGroupMembers[$gid])) {
112
-			return in_array($userDN, $this->cachedGroupMembers[$gid]);
113
-		}
114
-
115
-		$cacheKeyMembers = 'inGroup-members:' . $gid;
116
-		$members = $this->access->connection->getFromCache($cacheKeyMembers);
117
-		if (!is_null($members)) {
118
-			$this->cachedGroupMembers[$gid] = $members;
119
-			$isInGroup = in_array($userDN, $members, true);
120
-			$this->access->connection->writeToCache($cacheKey, $isInGroup);
121
-			return $isInGroup;
122
-		}
123
-
124
-		$groupDN = $this->access->groupname2dn($gid);
125
-		// just in case
126
-		if (!$groupDN || !$userDN) {
127
-			$this->access->connection->writeToCache($cacheKey, false);
128
-			return false;
129
-		}
130
-
131
-		//check primary group first
132
-		if ($gid === $this->getUserPrimaryGroup($userDN)) {
133
-			$this->access->connection->writeToCache($cacheKey, true);
134
-			return true;
135
-		}
136
-
137
-		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
138
-		$members = $this->_groupMembers($groupDN);
139
-
140
-		//extra work if we don't get back user DNs
141
-		switch ($this->ldapGroupMemberAssocAttr) {
142
-			case 'memberuid':
143
-			case 'zimbramailforwardingaddress':
144
-				$requestAttributes = $this->access->userManager->getAttributes(true);
145
-				$users = [];
146
-				$filterParts = [];
147
-				$bytes = 0;
148
-				foreach ($members as $mid) {
149
-					if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
150
-						$parts = explode('@', $mid); //making sure we get only the uid
151
-						$mid = $parts[0];
152
-					}
153
-					$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
154
-					$filterParts[] = $filter;
155
-					$bytes += strlen($filter);
156
-					if ($bytes >= 9000000) {
157
-						// AD has a default input buffer of 10 MB, we do not want
158
-						// to take even the chance to exceed it
159
-						// so we fetch results with the filterParts we collected so far
160
-						$filter = $this->access->combineFilterWithOr($filterParts);
161
-						$search = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
162
-						$bytes = 0;
163
-						$filterParts = [];
164
-						$users = array_merge($users, $search);
165
-					}
166
-				}
167
-
168
-				if (count($filterParts) > 0) {
169
-					// if there are filterParts left we need to add their result
170
-					$filter = $this->access->combineFilterWithOr($filterParts);
171
-					$search = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
172
-					$users = array_merge($users, $search);
173
-				}
174
-
175
-				// now we cleanup the users array to get only dns
176
-				$dns = [];
177
-				foreach ($users as $record) {
178
-					$dns[$record['dn'][0]] = 1;
179
-				}
180
-				$members = array_keys($dns);
181
-
182
-				break;
183
-		}
184
-
185
-		if (count($members) === 0) {
186
-			$this->access->connection->writeToCache($cacheKey, false);
187
-			return false;
188
-		}
189
-
190
-		$isInGroup = in_array($userDN, $members);
191
-		$this->access->connection->writeToCache($cacheKey, $isInGroup);
192
-		$this->access->connection->writeToCache($cacheKeyMembers, $members);
193
-		$this->cachedGroupMembers[$gid] = $members;
194
-
195
-		return $isInGroup;
196
-	}
197
-
198
-	/**
199
-	 * For a group that has user membership defined by an LDAP search url
200
-	 * attribute returns the users that match the search url otherwise returns
201
-	 * an empty array.
202
-	 *
203
-	 * @throws ServerNotAvailableException
204
-	 */
205
-	public function getDynamicGroupMembers(string $dnGroup): array {
206
-		$dynamicGroupMemberURL = strtolower((string)$this->access->connection->ldapDynamicGroupMemberURL);
207
-
208
-		if (empty($dynamicGroupMemberURL)) {
209
-			return [];
210
-		}
211
-
212
-		$dynamicMembers = [];
213
-		$memberURLs = $this->access->readAttribute(
214
-			$dnGroup,
215
-			$dynamicGroupMemberURL,
216
-			$this->access->connection->ldapGroupFilter
217
-		);
218
-		if ($memberURLs !== false) {
219
-			// this group has the 'memberURL' attribute so this is a dynamic group
220
-			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
221
-			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
222
-			$pos = strpos($memberURLs[0], '(');
223
-			if ($pos !== false) {
224
-				$memberUrlFilter = substr($memberURLs[0], $pos);
225
-				$foundMembers = $this->access->searchUsers($memberUrlFilter, ['dn']);
226
-				$dynamicMembers = [];
227
-				foreach ($foundMembers as $value) {
228
-					$dynamicMembers[$value['dn'][0]] = 1;
229
-				}
230
-			} else {
231
-				$this->logger->debug('No search filter found on member url of group {dn}',
232
-					[
233
-						'app' => 'user_ldap',
234
-						'dn' => $dnGroup,
235
-					]
236
-				);
237
-			}
238
-		}
239
-		return $dynamicMembers;
240
-	}
241
-
242
-	/**
243
-	 * @throws ServerNotAvailableException
244
-	 */
245
-	private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
246
-		if ($seen === null) {
247
-			$seen = [];
248
-			// the root entry has to be marked as processed to avoind infinit loops,
249
-			// but not included in the results laters on
250
-			$excludeFromResult = $dnGroup;
251
-		}
252
-		// cache only base groups, otherwise groups get additional unwarranted members
253
-		$shouldCacheResult = count($seen) === 0;
254
-
255
-		static $rawMemberReads = []; // runtime cache for intermediate ldap read results
256
-		$allMembers = [];
257
-
258
-		if (array_key_exists($dnGroup, $seen)) {
259
-			return [];
260
-		}
261
-		// used extensively in cron job, caching makes sense for nested groups
262
-		$cacheKey = '_groupMembers' . $dnGroup;
263
-		$groupMembers = $this->access->connection->getFromCache($cacheKey);
264
-		if ($groupMembers !== null) {
265
-			return $groupMembers;
266
-		}
267
-
268
-		if ($this->access->connection->ldapNestedGroups
269
-			&& $this->access->connection->useMemberOfToDetectMembership
270
-			&& $this->access->connection->hasMemberOfFilterSupport
271
-			&& $this->access->connection->ldapMatchingRuleInChainState !== Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE
272
-		) {
273
-			$attemptedLdapMatchingRuleInChain = true;
274
-			// compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others)
275
-			$filter = $this->access->combineFilterWithAnd([
276
-				$this->access->connection->ldapUserFilter,
277
-				$this->access->connection->ldapUserDisplayName . '=*',
278
-				'memberof:1.2.840.113556.1.4.1941:=' . $dnGroup
279
-			]);
280
-			$memberRecords = $this->access->fetchListOfUsers(
281
-				$filter,
282
-				$this->access->userManager->getAttributes(true)
283
-			);
284
-			$result = array_reduce($memberRecords, function ($carry, $record) {
285
-				$carry[] = $record['dn'][0];
286
-				return $carry;
287
-			}, []);
288
-			if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) {
289
-				return $result;
290
-			} elseif (!empty($memberRecords)) {
291
-				$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE;
292
-				$this->access->connection->saveConfiguration();
293
-				return $result;
294
-			}
295
-			// when feature availability is unknown, and the result is empty, continue and test with original approach
296
-		}
297
-
298
-		$seen[$dnGroup] = 1;
299
-		$members = $rawMemberReads[$dnGroup] ?? null;
300
-		if ($members === null) {
301
-			$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
302
-			$rawMemberReads[$dnGroup] = $members;
303
-		}
304
-		if (is_array($members)) {
305
-			$fetcher = function ($memberDN) use (&$seen) {
306
-				return $this->_groupMembers($memberDN, $seen);
307
-			};
308
-			$allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members, $seen);
309
-		}
310
-
311
-		$allMembers += $this->getDynamicGroupMembers($dnGroup);
312
-		if (isset($excludeFromResult)) {
313
-			$index = array_search($excludeFromResult, $allMembers, true);
314
-			if ($index !== false) {
315
-				unset($allMembers[$index]);
316
-			}
317
-		}
318
-
319
-		if ($shouldCacheResult) {
320
-			$this->access->connection->writeToCache($cacheKey, $allMembers);
321
-			unset($rawMemberReads[$dnGroup]);
322
-		}
323
-		if (isset($attemptedLdapMatchingRuleInChain)
324
-			&& $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN
325
-			&& !empty($allMembers)
326
-		) {
327
-			$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE;
328
-			$this->access->connection->saveConfiguration();
329
-		}
330
-		return $allMembers;
331
-	}
332
-
333
-	/**
334
-	 * @throws ServerNotAvailableException
335
-	 */
336
-	private function _getGroupDNsFromMemberOf(string $dn): array {
337
-		$groups = $this->access->readAttribute($dn, 'memberOf');
338
-		if (!is_array($groups)) {
339
-			return [];
340
-		}
341
-
342
-		$fetcher = function ($groupDN) {
343
-			if (isset($this->cachedNestedGroups[$groupDN])) {
344
-				$nestedGroups = $this->cachedNestedGroups[$groupDN];
345
-			} else {
346
-				$nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
347
-				if (!is_array($nestedGroups)) {
348
-					$nestedGroups = [];
349
-				}
350
-				$this->cachedNestedGroups[$groupDN] = $nestedGroups;
351
-			}
352
-			return $nestedGroups;
353
-		};
354
-
355
-		$groups = $this->walkNestedGroups($dn, $fetcher, $groups);
356
-		return $this->filterValidGroups($groups);
357
-	}
358
-
359
-	private function walkNestedGroups(string $dn, Closure $fetcher, array $list, array &$seen = []): array {
360
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
361
-		// depending on the input, we either have a list of DNs or a list of LDAP records
362
-		// also, the output expects either DNs or records. Testing the first element should suffice.
363
-		$recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
364
-
365
-		if ($nesting !== 1) {
366
-			if ($recordMode) {
367
-				// the keys are numeric, but should hold the DN
368
-				return array_reduce($list, function ($transformed, $record) use ($dn) {
369
-					if ($record['dn'][0] != $dn) {
370
-						$transformed[$record['dn'][0]] = $record;
371
-					}
372
-					return $transformed;
373
-				}, []);
374
-			}
375
-			return $list;
376
-		}
377
-
378
-		while ($record = array_shift($list)) {
379
-			$recordDN = $record['dn'][0] ?? $record;
380
-			if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
381
-				// Prevent loops
382
-				continue;
383
-			}
384
-			$fetched = $fetcher($record);
385
-			$list = array_merge($list, $fetched);
386
-			if (!isset($seen[$recordDN]) || is_bool($seen[$recordDN]) && is_array($record)) {
387
-				$seen[$recordDN] = $record;
388
-			}
389
-		}
390
-
391
-		// on record mode, filter out intermediate state
392
-		return $recordMode ? array_filter($seen, 'is_array') : array_keys($seen);
393
-	}
394
-
395
-	/**
396
-	 * translates a gidNumber into an ownCloud internal name
397
-	 *
398
-	 * @return string|bool
399
-	 * @throws Exception
400
-	 * @throws ServerNotAvailableException
401
-	 */
402
-	public function gidNumber2Name(string $gid, string $dn) {
403
-		$cacheKey = 'gidNumberToName' . $gid;
404
-		$groupName = $this->access->connection->getFromCache($cacheKey);
405
-		if (!is_null($groupName) && isset($groupName)) {
406
-			return $groupName;
407
-		}
408
-
409
-		//we need to get the DN from LDAP
410
-		$filter = $this->access->combineFilterWithAnd([
411
-			$this->access->connection->ldapGroupFilter,
412
-			'objectClass=posixGroup',
413
-			$this->access->connection->ldapGidNumber . '=' . $gid
414
-		]);
415
-		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
416
-	}
417
-
418
-	/**
419
-	 * @throws ServerNotAvailableException
420
-	 * @throws Exception
421
-	 */
422
-	private function getNameOfGroup(string $filter, string $cacheKey) {
423
-		$result = $this->access->searchGroups($filter, ['dn'], 1);
424
-		if (empty($result)) {
425
-			$this->access->connection->writeToCache($cacheKey, false);
426
-			return null;
427
-		}
428
-		$dn = $result[0]['dn'][0];
429
-
430
-		//and now the group name
431
-		//NOTE once we have separate Nextcloud group IDs and group names we can
432
-		//directly read the display name attribute instead of the DN
433
-		$name = $this->access->dn2groupname($dn);
434
-
435
-		$this->access->connection->writeToCache($cacheKey, $name);
436
-
437
-		return $name;
438
-	}
439
-
440
-	/**
441
-	 * returns the entry's gidNumber
442
-	 *
443
-	 * @return string|bool
444
-	 * @throws ServerNotAvailableException
445
-	 */
446
-	private function getEntryGidNumber(string $dn, string $attribute) {
447
-		$value = $this->access->readAttribute($dn, $attribute);
448
-		if (is_array($value) && !empty($value)) {
449
-			return $value[0];
450
-		}
451
-		return false;
452
-	}
453
-
454
-	/**
455
-	 * @return string|bool
456
-	 * @throws ServerNotAvailableException
457
-	 */
458
-	public function getGroupGidNumber(string $dn) {
459
-		return $this->getEntryGidNumber($dn, 'gidNumber');
460
-	}
461
-
462
-	/**
463
-	 * returns the user's gidNumber
464
-	 *
465
-	 * @return string|bool
466
-	 * @throws ServerNotAvailableException
467
-	 */
468
-	public function getUserGidNumber(string $dn) {
469
-		$gidNumber = false;
470
-		if ($this->access->connection->hasGidNumber) {
471
-			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
472
-			if ($gidNumber === false) {
473
-				$this->access->connection->hasGidNumber = false;
474
-			}
475
-		}
476
-		return $gidNumber;
477
-	}
478
-
479
-	/**
480
-	 * @throws ServerNotAvailableException
481
-	 * @throws Exception
482
-	 */
483
-	private function prepareFilterForUsersHasGidNumber(string $groupDN, string $search = ''): string {
484
-		$groupID = $this->getGroupGidNumber($groupDN);
485
-		if ($groupID === false) {
486
-			throw new Exception('Not a valid group');
487
-		}
488
-
489
-		$filterParts = [];
490
-		$filterParts[] = $this->access->getFilterForUserCount();
491
-		if ($search !== '') {
492
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
493
-		}
494
-		$filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
495
-
496
-		return $this->access->combineFilterWithAnd($filterParts);
497
-	}
498
-
499
-	/**
500
-	 * returns a list of users that have the given group as gid number
501
-	 *
502
-	 * @throws ServerNotAvailableException
503
-	 */
504
-	public function getUsersInGidNumber(
505
-		string $groupDN,
506
-		string $search = '',
507
-		?int $limit = -1,
508
-		?int $offset = 0
509
-	): array {
510
-		try {
511
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
512
-			$users = $this->access->fetchListOfUsers(
513
-				$filter,
514
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
515
-				$limit,
516
-				$offset
517
-			);
518
-			return $this->access->nextcloudUserNames($users);
519
-		} catch (ServerNotAvailableException $e) {
520
-			throw $e;
521
-		} catch (Exception $e) {
522
-			return [];
523
-		}
524
-	}
525
-
526
-	/**
527
-	 * @throws ServerNotAvailableException
528
-	 * @return bool
529
-	 */
530
-	public function getUserGroupByGid(string $dn) {
531
-		$groupID = $this->getUserGidNumber($dn);
532
-		if ($groupID !== false) {
533
-			$groupName = $this->gidNumber2Name($groupID, $dn);
534
-			if ($groupName !== false) {
535
-				return $groupName;
536
-			}
537
-		}
538
-
539
-		return false;
540
-	}
541
-
542
-	/**
543
-	 * translates a primary group ID into an Nextcloud internal name
544
-	 *
545
-	 * @return string|bool
546
-	 * @throws Exception
547
-	 * @throws ServerNotAvailableException
548
-	 */
549
-	public function primaryGroupID2Name(string $gid, string $dn) {
550
-		$cacheKey = 'primaryGroupIDtoName_' . $gid;
551
-		$groupName = $this->access->connection->getFromCache($cacheKey);
552
-		if (!is_null($groupName)) {
553
-			return $groupName;
554
-		}
555
-
556
-		$domainObjectSid = $this->access->getSID($dn);
557
-		if ($domainObjectSid === false) {
558
-			return false;
559
-		}
560
-
561
-		//we need to get the DN from LDAP
562
-		$filter = $this->access->combineFilterWithAnd([
563
-			$this->access->connection->ldapGroupFilter,
564
-			'objectsid=' . $domainObjectSid . '-' . $gid
565
-		]);
566
-		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
567
-	}
568
-
569
-	/**
570
-	 * returns the entry's primary group ID
571
-	 *
572
-	 * @return string|bool
573
-	 * @throws ServerNotAvailableException
574
-	 */
575
-	private function getEntryGroupID(string $dn, string $attribute) {
576
-		$value = $this->access->readAttribute($dn, $attribute);
577
-		if (is_array($value) && !empty($value)) {
578
-			return $value[0];
579
-		}
580
-		return false;
581
-	}
582
-
583
-	/**
584
-	 * @return string|bool
585
-	 * @throws ServerNotAvailableException
586
-	 */
587
-	public function getGroupPrimaryGroupID(string $dn) {
588
-		return $this->getEntryGroupID($dn, 'primaryGroupToken');
589
-	}
590
-
591
-	/**
592
-	 * @return string|bool
593
-	 * @throws ServerNotAvailableException
594
-	 */
595
-	public function getUserPrimaryGroupIDs(string $dn) {
596
-		$primaryGroupID = false;
597
-		if ($this->access->connection->hasPrimaryGroups) {
598
-			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
599
-			if ($primaryGroupID === false) {
600
-				$this->access->connection->hasPrimaryGroups = false;
601
-			}
602
-		}
603
-		return $primaryGroupID;
604
-	}
605
-
606
-	/**
607
-	 * @throws Exception
608
-	 * @throws ServerNotAvailableException
609
-	 */
610
-	private function prepareFilterForUsersInPrimaryGroup(string $groupDN, string $search = ''): string {
611
-		$groupID = $this->getGroupPrimaryGroupID($groupDN);
612
-		if ($groupID === false) {
613
-			throw new Exception('Not a valid group');
614
-		}
615
-
616
-		$filterParts = [];
617
-		$filterParts[] = $this->access->getFilterForUserCount();
618
-		if ($search !== '') {
619
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
620
-		}
621
-		$filterParts[] = 'primaryGroupID=' . $groupID;
622
-
623
-		return $this->access->combineFilterWithAnd($filterParts);
624
-	}
625
-
626
-	/**
627
-	 * @throws ServerNotAvailableException
628
-	 */
629
-	public function getUsersInPrimaryGroup(
630
-		string $groupDN,
631
-		string $search = '',
632
-		?int $limit = -1,
633
-		?int $offset = 0
634
-	): array {
635
-		try {
636
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
637
-			$users = $this->access->fetchListOfUsers(
638
-				$filter,
639
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
640
-				$limit,
641
-				$offset
642
-			);
643
-			return $this->access->nextcloudUserNames($users);
644
-		} catch (ServerNotAvailableException $e) {
645
-			throw $e;
646
-		} catch (Exception $e) {
647
-			return [];
648
-		}
649
-	}
650
-
651
-	/**
652
-	 * @throws ServerNotAvailableException
653
-	 */
654
-	public function countUsersInPrimaryGroup(
655
-		string $groupDN,
656
-		string $search = '',
657
-		int $limit = -1,
658
-		int $offset = 0
659
-	): int {
660
-		try {
661
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
662
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
663
-			return (int)$users;
664
-		} catch (ServerNotAvailableException $e) {
665
-			throw $e;
666
-		} catch (Exception $e) {
667
-			return 0;
668
-		}
669
-	}
670
-
671
-	/**
672
-	 * @return string|bool
673
-	 * @throws ServerNotAvailableException
674
-	 */
675
-	public function getUserPrimaryGroup(string $dn) {
676
-		$groupID = $this->getUserPrimaryGroupIDs($dn);
677
-		if ($groupID !== false) {
678
-			$groupName = $this->primaryGroupID2Name($groupID, $dn);
679
-			if ($groupName !== false) {
680
-				return $groupName;
681
-			}
682
-		}
683
-
684
-		return false;
685
-	}
686
-
687
-	/**
688
-	 * This function fetches all groups a user belongs to. It does not check
689
-	 * if the user exists at all.
690
-	 *
691
-	 * This function includes groups based on dynamic group membership.
692
-	 *
693
-	 * @param string $uid Name of the user
694
-	 * @return array with group names
695
-	 * @throws Exception
696
-	 * @throws ServerNotAvailableException
697
-	 */
698
-	public function getUserGroups($uid) {
699
-		if (!$this->enabled) {
700
-			return [];
701
-		}
702
-		$cacheKey = 'getUserGroups' . $uid;
703
-		$userGroups = $this->access->connection->getFromCache($cacheKey);
704
-		if (!is_null($userGroups)) {
705
-			return $userGroups;
706
-		}
707
-		$userDN = $this->access->username2dn($uid);
708
-		if (!$userDN) {
709
-			$this->access->connection->writeToCache($cacheKey, []);
710
-			return [];
711
-		}
712
-
713
-		$groups = [];
714
-		$primaryGroup = $this->getUserPrimaryGroup($userDN);
715
-		$gidGroupName = $this->getUserGroupByGid($userDN);
716
-
717
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
718
-
719
-		if (!empty($dynamicGroupMemberURL)) {
720
-			// look through dynamic groups to add them to the result array if needed
721
-			$groupsToMatch = $this->access->fetchListOfGroups(
722
-				$this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
723
-			foreach ($groupsToMatch as $dynamicGroup) {
724
-				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
725
-					continue;
726
-				}
727
-				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
728
-				if ($pos !== false) {
729
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
730
-					// apply filter via ldap search to see if this user is in this
731
-					// dynamic group
732
-					$userMatch = $this->access->readAttribute(
733
-						$userDN,
734
-						$this->access->connection->ldapUserDisplayName,
735
-						$memberUrlFilter
736
-					);
737
-					if ($userMatch !== false) {
738
-						// match found so this user is in this group
739
-						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
740
-						if (is_string($groupName)) {
741
-							// be sure to never return false if the dn could not be
742
-							// resolved to a name, for whatever reason.
743
-							$groups[] = $groupName;
744
-						}
745
-					}
746
-				} else {
747
-					$this->logger->debug('No search filter found on member url of group {dn}',
748
-						[
749
-							'app' => 'user_ldap',
750
-							'dn' => $dynamicGroup,
751
-						]
752
-					);
753
-				}
754
-			}
755
-		}
756
-
757
-		// if possible, read out membership via memberOf. It's far faster than
758
-		// performing a search, which still is a fallback later.
759
-		// memberof doesn't support memberuid, so skip it here.
760
-		if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
761
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
762
-			&& $this->ldapGroupMemberAssocAttr !== 'memberuid'
763
-			&& $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
764
-			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
765
-			if (is_array($groupDNs)) {
766
-				foreach ($groupDNs as $dn) {
767
-					$groupName = $this->access->dn2groupname($dn);
768
-					if (is_string($groupName)) {
769
-						// be sure to never return false if the dn could not be
770
-						// resolved to a name, for whatever reason.
771
-						$groups[] = $groupName;
772
-					}
773
-				}
774
-			}
775
-
776
-			if ($primaryGroup !== false) {
777
-				$groups[] = $primaryGroup;
778
-			}
779
-			if ($gidGroupName !== false) {
780
-				$groups[] = $gidGroupName;
781
-			}
782
-			$this->access->connection->writeToCache($cacheKey, $groups);
783
-			return $groups;
784
-		}
785
-
786
-		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
787
-		switch ($this->ldapGroupMemberAssocAttr) {
788
-			case 'uniquemember':
789
-			case 'member':
790
-				$uid = $userDN;
791
-				break;
792
-
793
-			case 'memberuid':
794
-			case 'zimbramailforwardingaddress':
795
-				$result = $this->access->readAttribute($userDN, 'uid');
796
-				if ($result === false) {
797
-					$this->logger->debug('No uid attribute found for DN {dn} on {host}',
798
-						[
799
-							'app' => 'user_ldap',
800
-							'dn' => $userDN,
801
-							'host' => $this->access->connection->ldapHost,
802
-						]
803
-					);
804
-					$uid = false;
805
-				} else {
806
-					$uid = $result[0];
807
-				}
808
-				break;
809
-
810
-			default:
811
-				// just in case
812
-				$uid = $userDN;
813
-				break;
814
-		}
815
-
816
-		if ($uid !== false) {
817
-			if (isset($this->cachedGroupsByMember[$uid])) {
818
-				$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
819
-			} else {
820
-				$groupsByMember = array_values($this->getGroupsByMember($uid));
821
-				$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
822
-				$this->cachedGroupsByMember[$uid] = $groupsByMember;
823
-				$groups = array_merge($groups, $groupsByMember);
824
-			}
825
-		}
826
-
827
-		if ($primaryGroup !== false) {
828
-			$groups[] = $primaryGroup;
829
-		}
830
-		if ($gidGroupName !== false) {
831
-			$groups[] = $gidGroupName;
832
-		}
833
-
834
-		$groups = array_unique($groups, SORT_LOCALE_STRING);
835
-		$this->access->connection->writeToCache($cacheKey, $groups);
836
-
837
-		return $groups;
838
-	}
839
-
840
-	/**
841
-	 * @throws ServerNotAvailableException
842
-	 */
843
-	private function getGroupsByMember(string $dn, array &$seen = null): array {
844
-		if ($seen === null) {
845
-			$seen = [];
846
-		}
847
-		if (array_key_exists($dn, $seen)) {
848
-			// avoid loops
849
-			return [];
850
-		}
851
-		$allGroups = [];
852
-		$seen[$dn] = true;
853
-		$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
854
-
855
-		if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
856
-			//in this case the member entries are email addresses
857
-			$filter .= '@*';
858
-		}
859
-
860
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
861
-		if ($nesting === 0) {
862
-			$filter = $this->access->combineFilterWithAnd([$filter, $this->access->connection->ldapGroupFilter]);
863
-		}
864
-
865
-		$groups = $this->access->fetchListOfGroups($filter,
866
-			[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
867
-		$fetcher = function ($dn) use (&$seen) {
868
-			if (is_array($dn) && isset($dn['dn'][0])) {
869
-				$dn = $dn['dn'][0];
870
-			}
871
-			return $this->getGroupsByMember($dn, $seen);
872
-		};
873
-
874
-		if (empty($dn)) {
875
-			$dn = "";
876
-		}
877
-
878
-		$allGroups = $this->walkNestedGroups($dn, $fetcher, $groups, $seen);
879
-		$visibleGroups = $this->filterValidGroups($allGroups);
880
-		return array_intersect_key($allGroups, $visibleGroups);
881
-	}
882
-
883
-	/**
884
-	 * get a list of all users in a group
885
-	 *
886
-	 * @param string $gid
887
-	 * @param string $search
888
-	 * @param int $limit
889
-	 * @param int $offset
890
-	 * @return array with user ids
891
-	 * @throws Exception
892
-	 * @throws ServerNotAvailableException
893
-	 */
894
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
895
-		if (!$this->enabled) {
896
-			return [];
897
-		}
898
-		if (!$this->groupExists($gid)) {
899
-			return [];
900
-		}
901
-		$search = $this->access->escapeFilterPart($search, true);
902
-		$cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
903
-		// check for cache of the exact query
904
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
905
-		if (!is_null($groupUsers)) {
906
-			return $groupUsers;
907
-		}
908
-
909
-		if ($limit === -1) {
910
-			$limit = null;
911
-		}
912
-		// check for cache of the query without limit and offset
913
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
914
-		if (!is_null($groupUsers)) {
915
-			$groupUsers = array_slice($groupUsers, $offset, $limit);
916
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
917
-			return $groupUsers;
918
-		}
919
-
920
-		$groupDN = $this->access->groupname2dn($gid);
921
-		if (!$groupDN) {
922
-			// group couldn't be found, return empty resultset
923
-			$this->access->connection->writeToCache($cacheKey, []);
924
-			return [];
925
-		}
926
-
927
-		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
928
-		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
929
-		$members = $this->_groupMembers($groupDN);
930
-		if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
931
-			//in case users could not be retrieved, return empty result set
932
-			$this->access->connection->writeToCache($cacheKey, []);
933
-			return [];
934
-		}
935
-
936
-		$groupUsers = [];
937
-		$attrs = $this->access->userManager->getAttributes(true);
938
-		foreach ($members as $member) {
939
-			switch ($this->ldapGroupMemberAssocAttr) {
940
-				/** @noinspection PhpMissingBreakStatementInspection */
941
-				case 'zimbramailforwardingaddress':
942
-					//we get email addresses and need to convert them to uids
943
-					$parts = explode('@', $member);
944
-					$member = $parts[0];
945
-					//no break needed because we just needed to remove the email part and now we have uids
946
-				case 'memberuid':
947
-					//we got uids, need to get their DNs to 'translate' them to user names
948
-					$filter = $this->access->combineFilterWithAnd([
949
-						str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
950
-						$this->access->combineFilterWithAnd([
951
-							$this->access->getFilterPartForUserSearch($search),
952
-							$this->access->connection->ldapUserFilter
953
-						])
954
-					]);
955
-					$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
956
-					if (empty($ldap_users)) {
957
-						break;
958
-					}
959
-					$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
960
-					break;
961
-				default:
962
-					//we got DNs, check if we need to filter by search or we can give back all of them
963
-					$uid = $this->access->dn2username($member);
964
-					if (!$uid) {
965
-						break;
966
-					}
967
-
968
-					$cacheKey = 'userExistsOnLDAP' . $uid;
969
-					$userExists = $this->access->connection->getFromCache($cacheKey);
970
-					if ($userExists === false) {
971
-						break;
972
-					}
973
-					if ($userExists === null || $search !== '') {
974
-						if (!$this->access->readAttribute($member,
975
-							$this->access->connection->ldapUserDisplayName,
976
-							$this->access->combineFilterWithAnd([
977
-								$this->access->getFilterPartForUserSearch($search),
978
-								$this->access->connection->ldapUserFilter
979
-							]))) {
980
-							if ($search === '') {
981
-								$this->access->connection->writeToCache($cacheKey, false);
982
-							}
983
-							break;
984
-						}
985
-						$this->access->connection->writeToCache($cacheKey, true);
986
-					}
987
-					$groupUsers[] = $uid;
988
-					break;
989
-			}
990
-		}
991
-
992
-		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
993
-		natsort($groupUsers);
994
-		$this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
995
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
996
-
997
-		$this->access->connection->writeToCache($cacheKey, $groupUsers);
998
-
999
-		return $groupUsers;
1000
-	}
1001
-
1002
-	/**
1003
-	 * returns the number of users in a group, who match the search term
1004
-	 *
1005
-	 * @param string $gid the internal group name
1006
-	 * @param string $search optional, a search string
1007
-	 * @return int|bool
1008
-	 * @throws Exception
1009
-	 * @throws ServerNotAvailableException
1010
-	 */
1011
-	public function countUsersInGroup($gid, $search = '') {
1012
-		if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
1013
-			return $this->groupPluginManager->countUsersInGroup($gid, $search);
1014
-		}
1015
-
1016
-		$cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
1017
-		if (!$this->enabled || !$this->groupExists($gid)) {
1018
-			return false;
1019
-		}
1020
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
1021
-		if (!is_null($groupUsers)) {
1022
-			return $groupUsers;
1023
-		}
1024
-
1025
-		$groupDN = $this->access->groupname2dn($gid);
1026
-		if (!$groupDN) {
1027
-			// group couldn't be found, return empty result set
1028
-			$this->access->connection->writeToCache($cacheKey, false);
1029
-			return false;
1030
-		}
1031
-
1032
-		$members = $this->_groupMembers($groupDN);
1033
-		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
1034
-		if (!$members && $primaryUserCount === 0) {
1035
-			//in case users could not be retrieved, return empty result set
1036
-			$this->access->connection->writeToCache($cacheKey, false);
1037
-			return false;
1038
-		}
1039
-
1040
-		if ($search === '') {
1041
-			$groupUsers = count($members) + $primaryUserCount;
1042
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
1043
-			return $groupUsers;
1044
-		}
1045
-		$search = $this->access->escapeFilterPart($search, true);
1046
-		$isMemberUid =
1047
-			($this->ldapGroupMemberAssocAttr === 'memberuid' ||
1048
-				$this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress');
1049
-
1050
-		//we need to apply the search filter
1051
-		//alternatives that need to be checked:
1052
-		//a) get all users by search filter and array_intersect them
1053
-		//b) a, but only when less than 1k 10k ?k users like it is
1054
-		//c) put all DNs|uids in a LDAP filter, combine with the search string
1055
-		//   and let it count.
1056
-		//For now this is not important, because the only use of this method
1057
-		//does not supply a search string
1058
-		$groupUsers = [];
1059
-		foreach ($members as $member) {
1060
-			if ($isMemberUid) {
1061
-				if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
1062
-					//we get email addresses and need to convert them to uids
1063
-					$parts = explode('@', $member);
1064
-					$member = $parts[0];
1065
-				}
1066
-				//we got uids, need to get their DNs to 'translate' them to user names
1067
-				$filter = $this->access->combineFilterWithAnd([
1068
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
1069
-					$this->access->getFilterPartForUserSearch($search)
1070
-				]);
1071
-				$ldap_users = $this->access->fetchListOfUsers($filter, ['dn'], 1);
1072
-				if (count($ldap_users) < 1) {
1073
-					continue;
1074
-				}
1075
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
1076
-			} else {
1077
-				//we need to apply the search filter now
1078
-				if (!$this->access->readAttribute($member,
1079
-					$this->access->connection->ldapUserDisplayName,
1080
-					$this->access->getFilterPartForUserSearch($search))) {
1081
-					continue;
1082
-				}
1083
-				// dn2username will also check if the users belong to the allowed base
1084
-				if ($ncGroupId = $this->access->dn2username($member)) {
1085
-					$groupUsers[] = $ncGroupId;
1086
-				}
1087
-			}
1088
-		}
1089
-
1090
-		//and get users that have the group as primary
1091
-		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1092
-
1093
-		return count($groupUsers) + $primaryUsers;
1094
-	}
1095
-
1096
-	/**
1097
-	 * get a list of all groups using a paged search
1098
-	 *
1099
-	 * @param string $search
1100
-	 * @param int $limit
1101
-	 * @param int $offset
1102
-	 * @return array with group names
1103
-	 *
1104
-	 * Returns a list with all groups
1105
-	 * Uses a paged search if available to override a
1106
-	 * server side search limit.
1107
-	 * (active directory has a limit of 1000 by default)
1108
-	 * @throws Exception
1109
-	 */
1110
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
1111
-		if (!$this->enabled) {
1112
-			return [];
1113
-		}
1114
-		$search = $this->access->escapeFilterPart($search, true);
1115
-		$cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1116
-
1117
-		//Check cache before driving unnecessary searches
1118
-		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
1119
-		if (!is_null($ldap_groups)) {
1120
-			return $ldap_groups;
1121
-		}
1122
-
1123
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
1124
-		// error. With a limit of 0, we get 0 results. So we pass null.
1125
-		if ($limit <= 0) {
1126
-			$limit = null;
1127
-		}
1128
-		$filter = $this->access->combineFilterWithAnd([
1129
-			$this->access->connection->ldapGroupFilter,
1130
-			$this->access->getFilterPartForGroupSearch($search)
1131
-		]);
1132
-		$ldap_groups = $this->access->fetchListOfGroups($filter,
1133
-			[$this->access->connection->ldapGroupDisplayName, 'dn'],
1134
-			$limit,
1135
-			$offset);
1136
-		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1137
-
1138
-		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
1139
-		return $ldap_groups;
1140
-	}
1141
-
1142
-	/**
1143
-	 * check if a group exists
1144
-	 *
1145
-	 * @param string $gid
1146
-	 * @return bool
1147
-	 * @throws ServerNotAvailableException
1148
-	 */
1149
-	public function groupExists($gid) {
1150
-		$groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1151
-		if (!is_null($groupExists)) {
1152
-			return (bool)$groupExists;
1153
-		}
1154
-
1155
-		//getting dn, if false the group does not exist. If dn, it may be mapped
1156
-		//only, requires more checking.
1157
-		$dn = $this->access->groupname2dn($gid);
1158
-		if (!$dn) {
1159
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1160
-			return false;
1161
-		}
1162
-
1163
-		if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1164
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1165
-			return false;
1166
-		}
1167
-
1168
-		//if group really still exists, we will be able to read its objectClass
1169
-		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1170
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1171
-			return false;
1172
-		}
1173
-
1174
-		$this->access->connection->writeToCache('groupExists' . $gid, true);
1175
-		return true;
1176
-	}
1177
-
1178
-	/**
1179
-	 * @throws ServerNotAvailableException
1180
-	 * @throws Exception
1181
-	 */
1182
-	protected function filterValidGroups(array $listOfGroups): array {
1183
-		$validGroupDNs = [];
1184
-		foreach ($listOfGroups as $key => $item) {
1185
-			$dn = is_string($item) ? $item : $item['dn'][0];
1186
-			$gid = $this->access->dn2groupname($dn);
1187
-			if (!$gid) {
1188
-				continue;
1189
-			}
1190
-			if ($this->groupExists($gid)) {
1191
-				$validGroupDNs[$key] = $item;
1192
-			}
1193
-		}
1194
-		return $validGroupDNs;
1195
-	}
1196
-
1197
-	/**
1198
-	 * Check if backend implements actions
1199
-	 *
1200
-	 * @param int $actions bitwise-or'ed actions
1201
-	 * @return boolean
1202
-	 *
1203
-	 * Returns the supported actions as int to be
1204
-	 * compared with GroupInterface::CREATE_GROUP etc.
1205
-	 */
1206
-	public function implementsActions($actions) {
1207
-		return (bool)((GroupInterface::COUNT_USERS |
1208
-				GroupInterface::DELETE_GROUP |
1209
-				$this->groupPluginManager->getImplementedActions()) & $actions);
1210
-	}
1211
-
1212
-	/**
1213
-	 * Return access for LDAP interaction.
1214
-	 *
1215
-	 * @return Access instance of Access for LDAP interaction
1216
-	 */
1217
-	public function getLDAPAccess($gid) {
1218
-		return $this->access;
1219
-	}
1220
-
1221
-	/**
1222
-	 * create a group
1223
-	 *
1224
-	 * @param string $gid
1225
-	 * @return bool
1226
-	 * @throws Exception
1227
-	 * @throws ServerNotAvailableException
1228
-	 */
1229
-	public function createGroup($gid) {
1230
-		if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1231
-			if ($dn = $this->groupPluginManager->createGroup($gid)) {
1232
-				//updates group mapping
1233
-				$uuid = $this->access->getUUID($dn, false);
1234
-				if (is_string($uuid)) {
1235
-					$this->access->mapAndAnnounceIfApplicable(
1236
-						$this->access->getGroupMapper(),
1237
-						$dn,
1238
-						$gid,
1239
-						$uuid,
1240
-						false
1241
-					);
1242
-					$this->access->cacheGroupExists($gid);
1243
-				}
1244
-			}
1245
-			return $dn != null;
1246
-		}
1247
-		throw new Exception('Could not create group in LDAP backend.');
1248
-	}
1249
-
1250
-	/**
1251
-	 * delete a group
1252
-	 *
1253
-	 * @param string $gid gid of the group to delete
1254
-	 * @throws Exception
1255
-	 */
1256
-	public function deleteGroup(string $gid): bool {
1257
-		if ($this->groupPluginManager->canDeleteGroup()) {
1258
-			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1259
-				// Delete group in nextcloud internal db
1260
-				$this->access->getGroupMapper()->unmap($gid);
1261
-				$this->access->connection->writeToCache("groupExists" . $gid, false);
1262
-			}
1263
-			return $ret;
1264
-		}
1265
-
1266
-		// Getting dn, if false the group is not mapped
1267
-		$dn = $this->access->groupname2dn($gid);
1268
-		if (!$dn) {
1269
-			throw new Exception('Could not delete unknown group '.$gid.' in LDAP backend.');
1270
-		}
1271
-
1272
-		if (!$this->groupExists($gid)) {
1273
-			// The group does not exist in the LDAP, remove the mapping
1274
-			$this->access->getGroupMapper()->unmap($gid);
1275
-			$this->access->connection->writeToCache("groupExists" . $gid, false);
1276
-			return true;
1277
-		}
1278
-
1279
-		throw new Exception('Could not delete existing group '.$gid.' in LDAP backend.');
1280
-	}
1281
-
1282
-	/**
1283
-	 * Add a user to a group
1284
-	 *
1285
-	 * @param string $uid Name of the user to add to group
1286
-	 * @param string $gid Name of the group in which add the user
1287
-	 * @return bool
1288
-	 * @throws Exception
1289
-	 */
1290
-	public function addToGroup($uid, $gid) {
1291
-		if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1292
-			if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1293
-				$this->access->connection->clearCache();
1294
-				unset($this->cachedGroupMembers[$gid]);
1295
-			}
1296
-			return $ret;
1297
-		}
1298
-		throw new Exception('Could not add user to group in LDAP backend.');
1299
-	}
1300
-
1301
-	/**
1302
-	 * Removes a user from a group
1303
-	 *
1304
-	 * @param string $uid Name of the user to remove from group
1305
-	 * @param string $gid Name of the group from which remove the user
1306
-	 * @return bool
1307
-	 * @throws Exception
1308
-	 */
1309
-	public function removeFromGroup($uid, $gid) {
1310
-		if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1311
-			if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1312
-				$this->access->connection->clearCache();
1313
-				unset($this->cachedGroupMembers[$gid]);
1314
-			}
1315
-			return $ret;
1316
-		}
1317
-		throw new Exception('Could not remove user from group in LDAP backend.');
1318
-	}
1319
-
1320
-	/**
1321
-	 * Gets group details
1322
-	 *
1323
-	 * @param string $gid Name of the group
1324
-	 * @return array|false
1325
-	 * @throws Exception
1326
-	 */
1327
-	public function getGroupDetails($gid) {
1328
-		if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1329
-			return $this->groupPluginManager->getGroupDetails($gid);
1330
-		}
1331
-		throw new Exception('Could not get group details in LDAP backend.');
1332
-	}
1333
-
1334
-	/**
1335
-	 * Return LDAP connection resource from a cloned connection.
1336
-	 * The cloned connection needs to be closed manually.
1337
-	 * of the current access.
1338
-	 *
1339
-	 * @param string $gid
1340
-	 * @return resource|\LDAP\Connection The LDAP connection
1341
-	 * @throws ServerNotAvailableException
1342
-	 */
1343
-	public function getNewLDAPConnection($gid) {
1344
-		$connection = clone $this->access->getConnection();
1345
-		return $connection->getConnectionResource();
1346
-	}
1347
-
1348
-	/**
1349
-	 * @throws ServerNotAvailableException
1350
-	 */
1351
-	public function getDisplayName(string $gid): string {
1352
-		if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1353
-			return $this->groupPluginManager->getDisplayName($gid);
1354
-		}
1355
-
1356
-		$cacheKey = 'group_getDisplayName' . $gid;
1357
-		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1358
-			return $displayName;
1359
-		}
1360
-
1361
-		$displayName = $this->access->readAttribute(
1362
-			$this->access->groupname2dn($gid),
1363
-			$this->access->connection->ldapGroupDisplayName);
1364
-
1365
-		if (($displayName !== false) && (count($displayName) > 0)) {
1366
-			$displayName = $displayName[0];
1367
-			$this->access->connection->writeToCache($cacheKey, $displayName);
1368
-			return $displayName;
1369
-		}
1370
-
1371
-		return '';
1372
-	}
56
+    protected $enabled = false;
57
+
58
+    /** @var string[][] $cachedGroupMembers array of users with gid as key */
59
+    protected $cachedGroupMembers;
60
+    /** @var string[] $cachedGroupsByMember array of groups with uid as key */
61
+    protected $cachedGroupsByMember;
62
+    /** @var string[] $cachedNestedGroups array of groups with gid (DN) as key */
63
+    protected $cachedNestedGroups;
64
+    /** @var GroupPluginManager */
65
+    protected $groupPluginManager;
66
+    /** @var LoggerInterface */
67
+    protected $logger;
68
+
69
+    /**
70
+     * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
71
+     */
72
+    protected $ldapGroupMemberAssocAttr;
73
+
74
+    public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
75
+        parent::__construct($access);
76
+        $filter = $this->access->connection->ldapGroupFilter;
77
+        $gAssoc = $this->access->connection->ldapGroupMemberAssocAttr;
78
+        if (!empty($filter) && !empty($gAssoc)) {
79
+            $this->enabled = true;
80
+        }
81
+
82
+        $this->cachedGroupMembers = new CappedMemoryCache();
83
+        $this->cachedGroupsByMember = new CappedMemoryCache();
84
+        $this->cachedNestedGroups = new CappedMemoryCache();
85
+        $this->groupPluginManager = $groupPluginManager;
86
+        $this->logger = OC::$server->get(LoggerInterface::class);
87
+        $this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc);
88
+    }
89
+
90
+    /**
91
+     * is user in group?
92
+     *
93
+     * @param string $uid uid of the user
94
+     * @param string $gid gid of the group
95
+     * @return bool
96
+     * @throws Exception
97
+     * @throws ServerNotAvailableException
98
+     */
99
+    public function inGroup($uid, $gid) {
100
+        if (!$this->enabled) {
101
+            return false;
102
+        }
103
+        $cacheKey = 'inGroup' . $uid . ':' . $gid;
104
+        $inGroup = $this->access->connection->getFromCache($cacheKey);
105
+        if (!is_null($inGroup)) {
106
+            return (bool)$inGroup;
107
+        }
108
+
109
+        $userDN = $this->access->username2dn($uid);
110
+
111
+        if (isset($this->cachedGroupMembers[$gid])) {
112
+            return in_array($userDN, $this->cachedGroupMembers[$gid]);
113
+        }
114
+
115
+        $cacheKeyMembers = 'inGroup-members:' . $gid;
116
+        $members = $this->access->connection->getFromCache($cacheKeyMembers);
117
+        if (!is_null($members)) {
118
+            $this->cachedGroupMembers[$gid] = $members;
119
+            $isInGroup = in_array($userDN, $members, true);
120
+            $this->access->connection->writeToCache($cacheKey, $isInGroup);
121
+            return $isInGroup;
122
+        }
123
+
124
+        $groupDN = $this->access->groupname2dn($gid);
125
+        // just in case
126
+        if (!$groupDN || !$userDN) {
127
+            $this->access->connection->writeToCache($cacheKey, false);
128
+            return false;
129
+        }
130
+
131
+        //check primary group first
132
+        if ($gid === $this->getUserPrimaryGroup($userDN)) {
133
+            $this->access->connection->writeToCache($cacheKey, true);
134
+            return true;
135
+        }
136
+
137
+        //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
138
+        $members = $this->_groupMembers($groupDN);
139
+
140
+        //extra work if we don't get back user DNs
141
+        switch ($this->ldapGroupMemberAssocAttr) {
142
+            case 'memberuid':
143
+            case 'zimbramailforwardingaddress':
144
+                $requestAttributes = $this->access->userManager->getAttributes(true);
145
+                $users = [];
146
+                $filterParts = [];
147
+                $bytes = 0;
148
+                foreach ($members as $mid) {
149
+                    if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
150
+                        $parts = explode('@', $mid); //making sure we get only the uid
151
+                        $mid = $parts[0];
152
+                    }
153
+                    $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
154
+                    $filterParts[] = $filter;
155
+                    $bytes += strlen($filter);
156
+                    if ($bytes >= 9000000) {
157
+                        // AD has a default input buffer of 10 MB, we do not want
158
+                        // to take even the chance to exceed it
159
+                        // so we fetch results with the filterParts we collected so far
160
+                        $filter = $this->access->combineFilterWithOr($filterParts);
161
+                        $search = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
162
+                        $bytes = 0;
163
+                        $filterParts = [];
164
+                        $users = array_merge($users, $search);
165
+                    }
166
+                }
167
+
168
+                if (count($filterParts) > 0) {
169
+                    // if there are filterParts left we need to add their result
170
+                    $filter = $this->access->combineFilterWithOr($filterParts);
171
+                    $search = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
172
+                    $users = array_merge($users, $search);
173
+                }
174
+
175
+                // now we cleanup the users array to get only dns
176
+                $dns = [];
177
+                foreach ($users as $record) {
178
+                    $dns[$record['dn'][0]] = 1;
179
+                }
180
+                $members = array_keys($dns);
181
+
182
+                break;
183
+        }
184
+
185
+        if (count($members) === 0) {
186
+            $this->access->connection->writeToCache($cacheKey, false);
187
+            return false;
188
+        }
189
+
190
+        $isInGroup = in_array($userDN, $members);
191
+        $this->access->connection->writeToCache($cacheKey, $isInGroup);
192
+        $this->access->connection->writeToCache($cacheKeyMembers, $members);
193
+        $this->cachedGroupMembers[$gid] = $members;
194
+
195
+        return $isInGroup;
196
+    }
197
+
198
+    /**
199
+     * For a group that has user membership defined by an LDAP search url
200
+     * attribute returns the users that match the search url otherwise returns
201
+     * an empty array.
202
+     *
203
+     * @throws ServerNotAvailableException
204
+     */
205
+    public function getDynamicGroupMembers(string $dnGroup): array {
206
+        $dynamicGroupMemberURL = strtolower((string)$this->access->connection->ldapDynamicGroupMemberURL);
207
+
208
+        if (empty($dynamicGroupMemberURL)) {
209
+            return [];
210
+        }
211
+
212
+        $dynamicMembers = [];
213
+        $memberURLs = $this->access->readAttribute(
214
+            $dnGroup,
215
+            $dynamicGroupMemberURL,
216
+            $this->access->connection->ldapGroupFilter
217
+        );
218
+        if ($memberURLs !== false) {
219
+            // this group has the 'memberURL' attribute so this is a dynamic group
220
+            // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
221
+            // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
222
+            $pos = strpos($memberURLs[0], '(');
223
+            if ($pos !== false) {
224
+                $memberUrlFilter = substr($memberURLs[0], $pos);
225
+                $foundMembers = $this->access->searchUsers($memberUrlFilter, ['dn']);
226
+                $dynamicMembers = [];
227
+                foreach ($foundMembers as $value) {
228
+                    $dynamicMembers[$value['dn'][0]] = 1;
229
+                }
230
+            } else {
231
+                $this->logger->debug('No search filter found on member url of group {dn}',
232
+                    [
233
+                        'app' => 'user_ldap',
234
+                        'dn' => $dnGroup,
235
+                    ]
236
+                );
237
+            }
238
+        }
239
+        return $dynamicMembers;
240
+    }
241
+
242
+    /**
243
+     * @throws ServerNotAvailableException
244
+     */
245
+    private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
246
+        if ($seen === null) {
247
+            $seen = [];
248
+            // the root entry has to be marked as processed to avoind infinit loops,
249
+            // but not included in the results laters on
250
+            $excludeFromResult = $dnGroup;
251
+        }
252
+        // cache only base groups, otherwise groups get additional unwarranted members
253
+        $shouldCacheResult = count($seen) === 0;
254
+
255
+        static $rawMemberReads = []; // runtime cache for intermediate ldap read results
256
+        $allMembers = [];
257
+
258
+        if (array_key_exists($dnGroup, $seen)) {
259
+            return [];
260
+        }
261
+        // used extensively in cron job, caching makes sense for nested groups
262
+        $cacheKey = '_groupMembers' . $dnGroup;
263
+        $groupMembers = $this->access->connection->getFromCache($cacheKey);
264
+        if ($groupMembers !== null) {
265
+            return $groupMembers;
266
+        }
267
+
268
+        if ($this->access->connection->ldapNestedGroups
269
+            && $this->access->connection->useMemberOfToDetectMembership
270
+            && $this->access->connection->hasMemberOfFilterSupport
271
+            && $this->access->connection->ldapMatchingRuleInChainState !== Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE
272
+        ) {
273
+            $attemptedLdapMatchingRuleInChain = true;
274
+            // compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others)
275
+            $filter = $this->access->combineFilterWithAnd([
276
+                $this->access->connection->ldapUserFilter,
277
+                $this->access->connection->ldapUserDisplayName . '=*',
278
+                'memberof:1.2.840.113556.1.4.1941:=' . $dnGroup
279
+            ]);
280
+            $memberRecords = $this->access->fetchListOfUsers(
281
+                $filter,
282
+                $this->access->userManager->getAttributes(true)
283
+            );
284
+            $result = array_reduce($memberRecords, function ($carry, $record) {
285
+                $carry[] = $record['dn'][0];
286
+                return $carry;
287
+            }, []);
288
+            if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) {
289
+                return $result;
290
+            } elseif (!empty($memberRecords)) {
291
+                $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE;
292
+                $this->access->connection->saveConfiguration();
293
+                return $result;
294
+            }
295
+            // when feature availability is unknown, and the result is empty, continue and test with original approach
296
+        }
297
+
298
+        $seen[$dnGroup] = 1;
299
+        $members = $rawMemberReads[$dnGroup] ?? null;
300
+        if ($members === null) {
301
+            $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
302
+            $rawMemberReads[$dnGroup] = $members;
303
+        }
304
+        if (is_array($members)) {
305
+            $fetcher = function ($memberDN) use (&$seen) {
306
+                return $this->_groupMembers($memberDN, $seen);
307
+            };
308
+            $allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members, $seen);
309
+        }
310
+
311
+        $allMembers += $this->getDynamicGroupMembers($dnGroup);
312
+        if (isset($excludeFromResult)) {
313
+            $index = array_search($excludeFromResult, $allMembers, true);
314
+            if ($index !== false) {
315
+                unset($allMembers[$index]);
316
+            }
317
+        }
318
+
319
+        if ($shouldCacheResult) {
320
+            $this->access->connection->writeToCache($cacheKey, $allMembers);
321
+            unset($rawMemberReads[$dnGroup]);
322
+        }
323
+        if (isset($attemptedLdapMatchingRuleInChain)
324
+            && $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN
325
+            && !empty($allMembers)
326
+        ) {
327
+            $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE;
328
+            $this->access->connection->saveConfiguration();
329
+        }
330
+        return $allMembers;
331
+    }
332
+
333
+    /**
334
+     * @throws ServerNotAvailableException
335
+     */
336
+    private function _getGroupDNsFromMemberOf(string $dn): array {
337
+        $groups = $this->access->readAttribute($dn, 'memberOf');
338
+        if (!is_array($groups)) {
339
+            return [];
340
+        }
341
+
342
+        $fetcher = function ($groupDN) {
343
+            if (isset($this->cachedNestedGroups[$groupDN])) {
344
+                $nestedGroups = $this->cachedNestedGroups[$groupDN];
345
+            } else {
346
+                $nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
347
+                if (!is_array($nestedGroups)) {
348
+                    $nestedGroups = [];
349
+                }
350
+                $this->cachedNestedGroups[$groupDN] = $nestedGroups;
351
+            }
352
+            return $nestedGroups;
353
+        };
354
+
355
+        $groups = $this->walkNestedGroups($dn, $fetcher, $groups);
356
+        return $this->filterValidGroups($groups);
357
+    }
358
+
359
+    private function walkNestedGroups(string $dn, Closure $fetcher, array $list, array &$seen = []): array {
360
+        $nesting = (int)$this->access->connection->ldapNestedGroups;
361
+        // depending on the input, we either have a list of DNs or a list of LDAP records
362
+        // also, the output expects either DNs or records. Testing the first element should suffice.
363
+        $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
364
+
365
+        if ($nesting !== 1) {
366
+            if ($recordMode) {
367
+                // the keys are numeric, but should hold the DN
368
+                return array_reduce($list, function ($transformed, $record) use ($dn) {
369
+                    if ($record['dn'][0] != $dn) {
370
+                        $transformed[$record['dn'][0]] = $record;
371
+                    }
372
+                    return $transformed;
373
+                }, []);
374
+            }
375
+            return $list;
376
+        }
377
+
378
+        while ($record = array_shift($list)) {
379
+            $recordDN = $record['dn'][0] ?? $record;
380
+            if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
381
+                // Prevent loops
382
+                continue;
383
+            }
384
+            $fetched = $fetcher($record);
385
+            $list = array_merge($list, $fetched);
386
+            if (!isset($seen[$recordDN]) || is_bool($seen[$recordDN]) && is_array($record)) {
387
+                $seen[$recordDN] = $record;
388
+            }
389
+        }
390
+
391
+        // on record mode, filter out intermediate state
392
+        return $recordMode ? array_filter($seen, 'is_array') : array_keys($seen);
393
+    }
394
+
395
+    /**
396
+     * translates a gidNumber into an ownCloud internal name
397
+     *
398
+     * @return string|bool
399
+     * @throws Exception
400
+     * @throws ServerNotAvailableException
401
+     */
402
+    public function gidNumber2Name(string $gid, string $dn) {
403
+        $cacheKey = 'gidNumberToName' . $gid;
404
+        $groupName = $this->access->connection->getFromCache($cacheKey);
405
+        if (!is_null($groupName) && isset($groupName)) {
406
+            return $groupName;
407
+        }
408
+
409
+        //we need to get the DN from LDAP
410
+        $filter = $this->access->combineFilterWithAnd([
411
+            $this->access->connection->ldapGroupFilter,
412
+            'objectClass=posixGroup',
413
+            $this->access->connection->ldapGidNumber . '=' . $gid
414
+        ]);
415
+        return $this->getNameOfGroup($filter, $cacheKey) ?? false;
416
+    }
417
+
418
+    /**
419
+     * @throws ServerNotAvailableException
420
+     * @throws Exception
421
+     */
422
+    private function getNameOfGroup(string $filter, string $cacheKey) {
423
+        $result = $this->access->searchGroups($filter, ['dn'], 1);
424
+        if (empty($result)) {
425
+            $this->access->connection->writeToCache($cacheKey, false);
426
+            return null;
427
+        }
428
+        $dn = $result[0]['dn'][0];
429
+
430
+        //and now the group name
431
+        //NOTE once we have separate Nextcloud group IDs and group names we can
432
+        //directly read the display name attribute instead of the DN
433
+        $name = $this->access->dn2groupname($dn);
434
+
435
+        $this->access->connection->writeToCache($cacheKey, $name);
436
+
437
+        return $name;
438
+    }
439
+
440
+    /**
441
+     * returns the entry's gidNumber
442
+     *
443
+     * @return string|bool
444
+     * @throws ServerNotAvailableException
445
+     */
446
+    private function getEntryGidNumber(string $dn, string $attribute) {
447
+        $value = $this->access->readAttribute($dn, $attribute);
448
+        if (is_array($value) && !empty($value)) {
449
+            return $value[0];
450
+        }
451
+        return false;
452
+    }
453
+
454
+    /**
455
+     * @return string|bool
456
+     * @throws ServerNotAvailableException
457
+     */
458
+    public function getGroupGidNumber(string $dn) {
459
+        return $this->getEntryGidNumber($dn, 'gidNumber');
460
+    }
461
+
462
+    /**
463
+     * returns the user's gidNumber
464
+     *
465
+     * @return string|bool
466
+     * @throws ServerNotAvailableException
467
+     */
468
+    public function getUserGidNumber(string $dn) {
469
+        $gidNumber = false;
470
+        if ($this->access->connection->hasGidNumber) {
471
+            $gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
472
+            if ($gidNumber === false) {
473
+                $this->access->connection->hasGidNumber = false;
474
+            }
475
+        }
476
+        return $gidNumber;
477
+    }
478
+
479
+    /**
480
+     * @throws ServerNotAvailableException
481
+     * @throws Exception
482
+     */
483
+    private function prepareFilterForUsersHasGidNumber(string $groupDN, string $search = ''): string {
484
+        $groupID = $this->getGroupGidNumber($groupDN);
485
+        if ($groupID === false) {
486
+            throw new Exception('Not a valid group');
487
+        }
488
+
489
+        $filterParts = [];
490
+        $filterParts[] = $this->access->getFilterForUserCount();
491
+        if ($search !== '') {
492
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
493
+        }
494
+        $filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
495
+
496
+        return $this->access->combineFilterWithAnd($filterParts);
497
+    }
498
+
499
+    /**
500
+     * returns a list of users that have the given group as gid number
501
+     *
502
+     * @throws ServerNotAvailableException
503
+     */
504
+    public function getUsersInGidNumber(
505
+        string $groupDN,
506
+        string $search = '',
507
+        ?int $limit = -1,
508
+        ?int $offset = 0
509
+    ): array {
510
+        try {
511
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
512
+            $users = $this->access->fetchListOfUsers(
513
+                $filter,
514
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
515
+                $limit,
516
+                $offset
517
+            );
518
+            return $this->access->nextcloudUserNames($users);
519
+        } catch (ServerNotAvailableException $e) {
520
+            throw $e;
521
+        } catch (Exception $e) {
522
+            return [];
523
+        }
524
+    }
525
+
526
+    /**
527
+     * @throws ServerNotAvailableException
528
+     * @return bool
529
+     */
530
+    public function getUserGroupByGid(string $dn) {
531
+        $groupID = $this->getUserGidNumber($dn);
532
+        if ($groupID !== false) {
533
+            $groupName = $this->gidNumber2Name($groupID, $dn);
534
+            if ($groupName !== false) {
535
+                return $groupName;
536
+            }
537
+        }
538
+
539
+        return false;
540
+    }
541
+
542
+    /**
543
+     * translates a primary group ID into an Nextcloud internal name
544
+     *
545
+     * @return string|bool
546
+     * @throws Exception
547
+     * @throws ServerNotAvailableException
548
+     */
549
+    public function primaryGroupID2Name(string $gid, string $dn) {
550
+        $cacheKey = 'primaryGroupIDtoName_' . $gid;
551
+        $groupName = $this->access->connection->getFromCache($cacheKey);
552
+        if (!is_null($groupName)) {
553
+            return $groupName;
554
+        }
555
+
556
+        $domainObjectSid = $this->access->getSID($dn);
557
+        if ($domainObjectSid === false) {
558
+            return false;
559
+        }
560
+
561
+        //we need to get the DN from LDAP
562
+        $filter = $this->access->combineFilterWithAnd([
563
+            $this->access->connection->ldapGroupFilter,
564
+            'objectsid=' . $domainObjectSid . '-' . $gid
565
+        ]);
566
+        return $this->getNameOfGroup($filter, $cacheKey) ?? false;
567
+    }
568
+
569
+    /**
570
+     * returns the entry's primary group ID
571
+     *
572
+     * @return string|bool
573
+     * @throws ServerNotAvailableException
574
+     */
575
+    private function getEntryGroupID(string $dn, string $attribute) {
576
+        $value = $this->access->readAttribute($dn, $attribute);
577
+        if (is_array($value) && !empty($value)) {
578
+            return $value[0];
579
+        }
580
+        return false;
581
+    }
582
+
583
+    /**
584
+     * @return string|bool
585
+     * @throws ServerNotAvailableException
586
+     */
587
+    public function getGroupPrimaryGroupID(string $dn) {
588
+        return $this->getEntryGroupID($dn, 'primaryGroupToken');
589
+    }
590
+
591
+    /**
592
+     * @return string|bool
593
+     * @throws ServerNotAvailableException
594
+     */
595
+    public function getUserPrimaryGroupIDs(string $dn) {
596
+        $primaryGroupID = false;
597
+        if ($this->access->connection->hasPrimaryGroups) {
598
+            $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
599
+            if ($primaryGroupID === false) {
600
+                $this->access->connection->hasPrimaryGroups = false;
601
+            }
602
+        }
603
+        return $primaryGroupID;
604
+    }
605
+
606
+    /**
607
+     * @throws Exception
608
+     * @throws ServerNotAvailableException
609
+     */
610
+    private function prepareFilterForUsersInPrimaryGroup(string $groupDN, string $search = ''): string {
611
+        $groupID = $this->getGroupPrimaryGroupID($groupDN);
612
+        if ($groupID === false) {
613
+            throw new Exception('Not a valid group');
614
+        }
615
+
616
+        $filterParts = [];
617
+        $filterParts[] = $this->access->getFilterForUserCount();
618
+        if ($search !== '') {
619
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
620
+        }
621
+        $filterParts[] = 'primaryGroupID=' . $groupID;
622
+
623
+        return $this->access->combineFilterWithAnd($filterParts);
624
+    }
625
+
626
+    /**
627
+     * @throws ServerNotAvailableException
628
+     */
629
+    public function getUsersInPrimaryGroup(
630
+        string $groupDN,
631
+        string $search = '',
632
+        ?int $limit = -1,
633
+        ?int $offset = 0
634
+    ): array {
635
+        try {
636
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
637
+            $users = $this->access->fetchListOfUsers(
638
+                $filter,
639
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
640
+                $limit,
641
+                $offset
642
+            );
643
+            return $this->access->nextcloudUserNames($users);
644
+        } catch (ServerNotAvailableException $e) {
645
+            throw $e;
646
+        } catch (Exception $e) {
647
+            return [];
648
+        }
649
+    }
650
+
651
+    /**
652
+     * @throws ServerNotAvailableException
653
+     */
654
+    public function countUsersInPrimaryGroup(
655
+        string $groupDN,
656
+        string $search = '',
657
+        int $limit = -1,
658
+        int $offset = 0
659
+    ): int {
660
+        try {
661
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
662
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
663
+            return (int)$users;
664
+        } catch (ServerNotAvailableException $e) {
665
+            throw $e;
666
+        } catch (Exception $e) {
667
+            return 0;
668
+        }
669
+    }
670
+
671
+    /**
672
+     * @return string|bool
673
+     * @throws ServerNotAvailableException
674
+     */
675
+    public function getUserPrimaryGroup(string $dn) {
676
+        $groupID = $this->getUserPrimaryGroupIDs($dn);
677
+        if ($groupID !== false) {
678
+            $groupName = $this->primaryGroupID2Name($groupID, $dn);
679
+            if ($groupName !== false) {
680
+                return $groupName;
681
+            }
682
+        }
683
+
684
+        return false;
685
+    }
686
+
687
+    /**
688
+     * This function fetches all groups a user belongs to. It does not check
689
+     * if the user exists at all.
690
+     *
691
+     * This function includes groups based on dynamic group membership.
692
+     *
693
+     * @param string $uid Name of the user
694
+     * @return array with group names
695
+     * @throws Exception
696
+     * @throws ServerNotAvailableException
697
+     */
698
+    public function getUserGroups($uid) {
699
+        if (!$this->enabled) {
700
+            return [];
701
+        }
702
+        $cacheKey = 'getUserGroups' . $uid;
703
+        $userGroups = $this->access->connection->getFromCache($cacheKey);
704
+        if (!is_null($userGroups)) {
705
+            return $userGroups;
706
+        }
707
+        $userDN = $this->access->username2dn($uid);
708
+        if (!$userDN) {
709
+            $this->access->connection->writeToCache($cacheKey, []);
710
+            return [];
711
+        }
712
+
713
+        $groups = [];
714
+        $primaryGroup = $this->getUserPrimaryGroup($userDN);
715
+        $gidGroupName = $this->getUserGroupByGid($userDN);
716
+
717
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
718
+
719
+        if (!empty($dynamicGroupMemberURL)) {
720
+            // look through dynamic groups to add them to the result array if needed
721
+            $groupsToMatch = $this->access->fetchListOfGroups(
722
+                $this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
723
+            foreach ($groupsToMatch as $dynamicGroup) {
724
+                if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
725
+                    continue;
726
+                }
727
+                $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
728
+                if ($pos !== false) {
729
+                    $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
730
+                    // apply filter via ldap search to see if this user is in this
731
+                    // dynamic group
732
+                    $userMatch = $this->access->readAttribute(
733
+                        $userDN,
734
+                        $this->access->connection->ldapUserDisplayName,
735
+                        $memberUrlFilter
736
+                    );
737
+                    if ($userMatch !== false) {
738
+                        // match found so this user is in this group
739
+                        $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
740
+                        if (is_string($groupName)) {
741
+                            // be sure to never return false if the dn could not be
742
+                            // resolved to a name, for whatever reason.
743
+                            $groups[] = $groupName;
744
+                        }
745
+                    }
746
+                } else {
747
+                    $this->logger->debug('No search filter found on member url of group {dn}',
748
+                        [
749
+                            'app' => 'user_ldap',
750
+                            'dn' => $dynamicGroup,
751
+                        ]
752
+                    );
753
+                }
754
+            }
755
+        }
756
+
757
+        // if possible, read out membership via memberOf. It's far faster than
758
+        // performing a search, which still is a fallback later.
759
+        // memberof doesn't support memberuid, so skip it here.
760
+        if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
761
+            && (int)$this->access->connection->useMemberOfToDetectMembership === 1
762
+            && $this->ldapGroupMemberAssocAttr !== 'memberuid'
763
+            && $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
764
+            $groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
765
+            if (is_array($groupDNs)) {
766
+                foreach ($groupDNs as $dn) {
767
+                    $groupName = $this->access->dn2groupname($dn);
768
+                    if (is_string($groupName)) {
769
+                        // be sure to never return false if the dn could not be
770
+                        // resolved to a name, for whatever reason.
771
+                        $groups[] = $groupName;
772
+                    }
773
+                }
774
+            }
775
+
776
+            if ($primaryGroup !== false) {
777
+                $groups[] = $primaryGroup;
778
+            }
779
+            if ($gidGroupName !== false) {
780
+                $groups[] = $gidGroupName;
781
+            }
782
+            $this->access->connection->writeToCache($cacheKey, $groups);
783
+            return $groups;
784
+        }
785
+
786
+        //uniqueMember takes DN, memberuid the uid, so we need to distinguish
787
+        switch ($this->ldapGroupMemberAssocAttr) {
788
+            case 'uniquemember':
789
+            case 'member':
790
+                $uid = $userDN;
791
+                break;
792
+
793
+            case 'memberuid':
794
+            case 'zimbramailforwardingaddress':
795
+                $result = $this->access->readAttribute($userDN, 'uid');
796
+                if ($result === false) {
797
+                    $this->logger->debug('No uid attribute found for DN {dn} on {host}',
798
+                        [
799
+                            'app' => 'user_ldap',
800
+                            'dn' => $userDN,
801
+                            'host' => $this->access->connection->ldapHost,
802
+                        ]
803
+                    );
804
+                    $uid = false;
805
+                } else {
806
+                    $uid = $result[0];
807
+                }
808
+                break;
809
+
810
+            default:
811
+                // just in case
812
+                $uid = $userDN;
813
+                break;
814
+        }
815
+
816
+        if ($uid !== false) {
817
+            if (isset($this->cachedGroupsByMember[$uid])) {
818
+                $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
819
+            } else {
820
+                $groupsByMember = array_values($this->getGroupsByMember($uid));
821
+                $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
822
+                $this->cachedGroupsByMember[$uid] = $groupsByMember;
823
+                $groups = array_merge($groups, $groupsByMember);
824
+            }
825
+        }
826
+
827
+        if ($primaryGroup !== false) {
828
+            $groups[] = $primaryGroup;
829
+        }
830
+        if ($gidGroupName !== false) {
831
+            $groups[] = $gidGroupName;
832
+        }
833
+
834
+        $groups = array_unique($groups, SORT_LOCALE_STRING);
835
+        $this->access->connection->writeToCache($cacheKey, $groups);
836
+
837
+        return $groups;
838
+    }
839
+
840
+    /**
841
+     * @throws ServerNotAvailableException
842
+     */
843
+    private function getGroupsByMember(string $dn, array &$seen = null): array {
844
+        if ($seen === null) {
845
+            $seen = [];
846
+        }
847
+        if (array_key_exists($dn, $seen)) {
848
+            // avoid loops
849
+            return [];
850
+        }
851
+        $allGroups = [];
852
+        $seen[$dn] = true;
853
+        $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
854
+
855
+        if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
856
+            //in this case the member entries are email addresses
857
+            $filter .= '@*';
858
+        }
859
+
860
+        $nesting = (int)$this->access->connection->ldapNestedGroups;
861
+        if ($nesting === 0) {
862
+            $filter = $this->access->combineFilterWithAnd([$filter, $this->access->connection->ldapGroupFilter]);
863
+        }
864
+
865
+        $groups = $this->access->fetchListOfGroups($filter,
866
+            [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
867
+        $fetcher = function ($dn) use (&$seen) {
868
+            if (is_array($dn) && isset($dn['dn'][0])) {
869
+                $dn = $dn['dn'][0];
870
+            }
871
+            return $this->getGroupsByMember($dn, $seen);
872
+        };
873
+
874
+        if (empty($dn)) {
875
+            $dn = "";
876
+        }
877
+
878
+        $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups, $seen);
879
+        $visibleGroups = $this->filterValidGroups($allGroups);
880
+        return array_intersect_key($allGroups, $visibleGroups);
881
+    }
882
+
883
+    /**
884
+     * get a list of all users in a group
885
+     *
886
+     * @param string $gid
887
+     * @param string $search
888
+     * @param int $limit
889
+     * @param int $offset
890
+     * @return array with user ids
891
+     * @throws Exception
892
+     * @throws ServerNotAvailableException
893
+     */
894
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
895
+        if (!$this->enabled) {
896
+            return [];
897
+        }
898
+        if (!$this->groupExists($gid)) {
899
+            return [];
900
+        }
901
+        $search = $this->access->escapeFilterPart($search, true);
902
+        $cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
903
+        // check for cache of the exact query
904
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
905
+        if (!is_null($groupUsers)) {
906
+            return $groupUsers;
907
+        }
908
+
909
+        if ($limit === -1) {
910
+            $limit = null;
911
+        }
912
+        // check for cache of the query without limit and offset
913
+        $groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
914
+        if (!is_null($groupUsers)) {
915
+            $groupUsers = array_slice($groupUsers, $offset, $limit);
916
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
917
+            return $groupUsers;
918
+        }
919
+
920
+        $groupDN = $this->access->groupname2dn($gid);
921
+        if (!$groupDN) {
922
+            // group couldn't be found, return empty resultset
923
+            $this->access->connection->writeToCache($cacheKey, []);
924
+            return [];
925
+        }
926
+
927
+        $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
928
+        $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
929
+        $members = $this->_groupMembers($groupDN);
930
+        if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
931
+            //in case users could not be retrieved, return empty result set
932
+            $this->access->connection->writeToCache($cacheKey, []);
933
+            return [];
934
+        }
935
+
936
+        $groupUsers = [];
937
+        $attrs = $this->access->userManager->getAttributes(true);
938
+        foreach ($members as $member) {
939
+            switch ($this->ldapGroupMemberAssocAttr) {
940
+                /** @noinspection PhpMissingBreakStatementInspection */
941
+                case 'zimbramailforwardingaddress':
942
+                    //we get email addresses and need to convert them to uids
943
+                    $parts = explode('@', $member);
944
+                    $member = $parts[0];
945
+                    //no break needed because we just needed to remove the email part and now we have uids
946
+                case 'memberuid':
947
+                    //we got uids, need to get their DNs to 'translate' them to user names
948
+                    $filter = $this->access->combineFilterWithAnd([
949
+                        str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
950
+                        $this->access->combineFilterWithAnd([
951
+                            $this->access->getFilterPartForUserSearch($search),
952
+                            $this->access->connection->ldapUserFilter
953
+                        ])
954
+                    ]);
955
+                    $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
956
+                    if (empty($ldap_users)) {
957
+                        break;
958
+                    }
959
+                    $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
960
+                    break;
961
+                default:
962
+                    //we got DNs, check if we need to filter by search or we can give back all of them
963
+                    $uid = $this->access->dn2username($member);
964
+                    if (!$uid) {
965
+                        break;
966
+                    }
967
+
968
+                    $cacheKey = 'userExistsOnLDAP' . $uid;
969
+                    $userExists = $this->access->connection->getFromCache($cacheKey);
970
+                    if ($userExists === false) {
971
+                        break;
972
+                    }
973
+                    if ($userExists === null || $search !== '') {
974
+                        if (!$this->access->readAttribute($member,
975
+                            $this->access->connection->ldapUserDisplayName,
976
+                            $this->access->combineFilterWithAnd([
977
+                                $this->access->getFilterPartForUserSearch($search),
978
+                                $this->access->connection->ldapUserFilter
979
+                            ]))) {
980
+                            if ($search === '') {
981
+                                $this->access->connection->writeToCache($cacheKey, false);
982
+                            }
983
+                            break;
984
+                        }
985
+                        $this->access->connection->writeToCache($cacheKey, true);
986
+                    }
987
+                    $groupUsers[] = $uid;
988
+                    break;
989
+            }
990
+        }
991
+
992
+        $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
993
+        natsort($groupUsers);
994
+        $this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
995
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
996
+
997
+        $this->access->connection->writeToCache($cacheKey, $groupUsers);
998
+
999
+        return $groupUsers;
1000
+    }
1001
+
1002
+    /**
1003
+     * returns the number of users in a group, who match the search term
1004
+     *
1005
+     * @param string $gid the internal group name
1006
+     * @param string $search optional, a search string
1007
+     * @return int|bool
1008
+     * @throws Exception
1009
+     * @throws ServerNotAvailableException
1010
+     */
1011
+    public function countUsersInGroup($gid, $search = '') {
1012
+        if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
1013
+            return $this->groupPluginManager->countUsersInGroup($gid, $search);
1014
+        }
1015
+
1016
+        $cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
1017
+        if (!$this->enabled || !$this->groupExists($gid)) {
1018
+            return false;
1019
+        }
1020
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
1021
+        if (!is_null($groupUsers)) {
1022
+            return $groupUsers;
1023
+        }
1024
+
1025
+        $groupDN = $this->access->groupname2dn($gid);
1026
+        if (!$groupDN) {
1027
+            // group couldn't be found, return empty result set
1028
+            $this->access->connection->writeToCache($cacheKey, false);
1029
+            return false;
1030
+        }
1031
+
1032
+        $members = $this->_groupMembers($groupDN);
1033
+        $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
1034
+        if (!$members && $primaryUserCount === 0) {
1035
+            //in case users could not be retrieved, return empty result set
1036
+            $this->access->connection->writeToCache($cacheKey, false);
1037
+            return false;
1038
+        }
1039
+
1040
+        if ($search === '') {
1041
+            $groupUsers = count($members) + $primaryUserCount;
1042
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
1043
+            return $groupUsers;
1044
+        }
1045
+        $search = $this->access->escapeFilterPart($search, true);
1046
+        $isMemberUid =
1047
+            ($this->ldapGroupMemberAssocAttr === 'memberuid' ||
1048
+                $this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress');
1049
+
1050
+        //we need to apply the search filter
1051
+        //alternatives that need to be checked:
1052
+        //a) get all users by search filter and array_intersect them
1053
+        //b) a, but only when less than 1k 10k ?k users like it is
1054
+        //c) put all DNs|uids in a LDAP filter, combine with the search string
1055
+        //   and let it count.
1056
+        //For now this is not important, because the only use of this method
1057
+        //does not supply a search string
1058
+        $groupUsers = [];
1059
+        foreach ($members as $member) {
1060
+            if ($isMemberUid) {
1061
+                if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
1062
+                    //we get email addresses and need to convert them to uids
1063
+                    $parts = explode('@', $member);
1064
+                    $member = $parts[0];
1065
+                }
1066
+                //we got uids, need to get their DNs to 'translate' them to user names
1067
+                $filter = $this->access->combineFilterWithAnd([
1068
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
1069
+                    $this->access->getFilterPartForUserSearch($search)
1070
+                ]);
1071
+                $ldap_users = $this->access->fetchListOfUsers($filter, ['dn'], 1);
1072
+                if (count($ldap_users) < 1) {
1073
+                    continue;
1074
+                }
1075
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]);
1076
+            } else {
1077
+                //we need to apply the search filter now
1078
+                if (!$this->access->readAttribute($member,
1079
+                    $this->access->connection->ldapUserDisplayName,
1080
+                    $this->access->getFilterPartForUserSearch($search))) {
1081
+                    continue;
1082
+                }
1083
+                // dn2username will also check if the users belong to the allowed base
1084
+                if ($ncGroupId = $this->access->dn2username($member)) {
1085
+                    $groupUsers[] = $ncGroupId;
1086
+                }
1087
+            }
1088
+        }
1089
+
1090
+        //and get users that have the group as primary
1091
+        $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1092
+
1093
+        return count($groupUsers) + $primaryUsers;
1094
+    }
1095
+
1096
+    /**
1097
+     * get a list of all groups using a paged search
1098
+     *
1099
+     * @param string $search
1100
+     * @param int $limit
1101
+     * @param int $offset
1102
+     * @return array with group names
1103
+     *
1104
+     * Returns a list with all groups
1105
+     * Uses a paged search if available to override a
1106
+     * server side search limit.
1107
+     * (active directory has a limit of 1000 by default)
1108
+     * @throws Exception
1109
+     */
1110
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
1111
+        if (!$this->enabled) {
1112
+            return [];
1113
+        }
1114
+        $search = $this->access->escapeFilterPart($search, true);
1115
+        $cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1116
+
1117
+        //Check cache before driving unnecessary searches
1118
+        $ldap_groups = $this->access->connection->getFromCache($cacheKey);
1119
+        if (!is_null($ldap_groups)) {
1120
+            return $ldap_groups;
1121
+        }
1122
+
1123
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
1124
+        // error. With a limit of 0, we get 0 results. So we pass null.
1125
+        if ($limit <= 0) {
1126
+            $limit = null;
1127
+        }
1128
+        $filter = $this->access->combineFilterWithAnd([
1129
+            $this->access->connection->ldapGroupFilter,
1130
+            $this->access->getFilterPartForGroupSearch($search)
1131
+        ]);
1132
+        $ldap_groups = $this->access->fetchListOfGroups($filter,
1133
+            [$this->access->connection->ldapGroupDisplayName, 'dn'],
1134
+            $limit,
1135
+            $offset);
1136
+        $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1137
+
1138
+        $this->access->connection->writeToCache($cacheKey, $ldap_groups);
1139
+        return $ldap_groups;
1140
+    }
1141
+
1142
+    /**
1143
+     * check if a group exists
1144
+     *
1145
+     * @param string $gid
1146
+     * @return bool
1147
+     * @throws ServerNotAvailableException
1148
+     */
1149
+    public function groupExists($gid) {
1150
+        $groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1151
+        if (!is_null($groupExists)) {
1152
+            return (bool)$groupExists;
1153
+        }
1154
+
1155
+        //getting dn, if false the group does not exist. If dn, it may be mapped
1156
+        //only, requires more checking.
1157
+        $dn = $this->access->groupname2dn($gid);
1158
+        if (!$dn) {
1159
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1160
+            return false;
1161
+        }
1162
+
1163
+        if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1164
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1165
+            return false;
1166
+        }
1167
+
1168
+        //if group really still exists, we will be able to read its objectClass
1169
+        if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1170
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1171
+            return false;
1172
+        }
1173
+
1174
+        $this->access->connection->writeToCache('groupExists' . $gid, true);
1175
+        return true;
1176
+    }
1177
+
1178
+    /**
1179
+     * @throws ServerNotAvailableException
1180
+     * @throws Exception
1181
+     */
1182
+    protected function filterValidGroups(array $listOfGroups): array {
1183
+        $validGroupDNs = [];
1184
+        foreach ($listOfGroups as $key => $item) {
1185
+            $dn = is_string($item) ? $item : $item['dn'][0];
1186
+            $gid = $this->access->dn2groupname($dn);
1187
+            if (!$gid) {
1188
+                continue;
1189
+            }
1190
+            if ($this->groupExists($gid)) {
1191
+                $validGroupDNs[$key] = $item;
1192
+            }
1193
+        }
1194
+        return $validGroupDNs;
1195
+    }
1196
+
1197
+    /**
1198
+     * Check if backend implements actions
1199
+     *
1200
+     * @param int $actions bitwise-or'ed actions
1201
+     * @return boolean
1202
+     *
1203
+     * Returns the supported actions as int to be
1204
+     * compared with GroupInterface::CREATE_GROUP etc.
1205
+     */
1206
+    public function implementsActions($actions) {
1207
+        return (bool)((GroupInterface::COUNT_USERS |
1208
+                GroupInterface::DELETE_GROUP |
1209
+                $this->groupPluginManager->getImplementedActions()) & $actions);
1210
+    }
1211
+
1212
+    /**
1213
+     * Return access for LDAP interaction.
1214
+     *
1215
+     * @return Access instance of Access for LDAP interaction
1216
+     */
1217
+    public function getLDAPAccess($gid) {
1218
+        return $this->access;
1219
+    }
1220
+
1221
+    /**
1222
+     * create a group
1223
+     *
1224
+     * @param string $gid
1225
+     * @return bool
1226
+     * @throws Exception
1227
+     * @throws ServerNotAvailableException
1228
+     */
1229
+    public function createGroup($gid) {
1230
+        if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1231
+            if ($dn = $this->groupPluginManager->createGroup($gid)) {
1232
+                //updates group mapping
1233
+                $uuid = $this->access->getUUID($dn, false);
1234
+                if (is_string($uuid)) {
1235
+                    $this->access->mapAndAnnounceIfApplicable(
1236
+                        $this->access->getGroupMapper(),
1237
+                        $dn,
1238
+                        $gid,
1239
+                        $uuid,
1240
+                        false
1241
+                    );
1242
+                    $this->access->cacheGroupExists($gid);
1243
+                }
1244
+            }
1245
+            return $dn != null;
1246
+        }
1247
+        throw new Exception('Could not create group in LDAP backend.');
1248
+    }
1249
+
1250
+    /**
1251
+     * delete a group
1252
+     *
1253
+     * @param string $gid gid of the group to delete
1254
+     * @throws Exception
1255
+     */
1256
+    public function deleteGroup(string $gid): bool {
1257
+        if ($this->groupPluginManager->canDeleteGroup()) {
1258
+            if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1259
+                // Delete group in nextcloud internal db
1260
+                $this->access->getGroupMapper()->unmap($gid);
1261
+                $this->access->connection->writeToCache("groupExists" . $gid, false);
1262
+            }
1263
+            return $ret;
1264
+        }
1265
+
1266
+        // Getting dn, if false the group is not mapped
1267
+        $dn = $this->access->groupname2dn($gid);
1268
+        if (!$dn) {
1269
+            throw new Exception('Could not delete unknown group '.$gid.' in LDAP backend.');
1270
+        }
1271
+
1272
+        if (!$this->groupExists($gid)) {
1273
+            // The group does not exist in the LDAP, remove the mapping
1274
+            $this->access->getGroupMapper()->unmap($gid);
1275
+            $this->access->connection->writeToCache("groupExists" . $gid, false);
1276
+            return true;
1277
+        }
1278
+
1279
+        throw new Exception('Could not delete existing group '.$gid.' in LDAP backend.');
1280
+    }
1281
+
1282
+    /**
1283
+     * Add a user to a group
1284
+     *
1285
+     * @param string $uid Name of the user to add to group
1286
+     * @param string $gid Name of the group in which add the user
1287
+     * @return bool
1288
+     * @throws Exception
1289
+     */
1290
+    public function addToGroup($uid, $gid) {
1291
+        if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1292
+            if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1293
+                $this->access->connection->clearCache();
1294
+                unset($this->cachedGroupMembers[$gid]);
1295
+            }
1296
+            return $ret;
1297
+        }
1298
+        throw new Exception('Could not add user to group in LDAP backend.');
1299
+    }
1300
+
1301
+    /**
1302
+     * Removes a user from a group
1303
+     *
1304
+     * @param string $uid Name of the user to remove from group
1305
+     * @param string $gid Name of the group from which remove the user
1306
+     * @return bool
1307
+     * @throws Exception
1308
+     */
1309
+    public function removeFromGroup($uid, $gid) {
1310
+        if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1311
+            if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1312
+                $this->access->connection->clearCache();
1313
+                unset($this->cachedGroupMembers[$gid]);
1314
+            }
1315
+            return $ret;
1316
+        }
1317
+        throw new Exception('Could not remove user from group in LDAP backend.');
1318
+    }
1319
+
1320
+    /**
1321
+     * Gets group details
1322
+     *
1323
+     * @param string $gid Name of the group
1324
+     * @return array|false
1325
+     * @throws Exception
1326
+     */
1327
+    public function getGroupDetails($gid) {
1328
+        if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1329
+            return $this->groupPluginManager->getGroupDetails($gid);
1330
+        }
1331
+        throw new Exception('Could not get group details in LDAP backend.');
1332
+    }
1333
+
1334
+    /**
1335
+     * Return LDAP connection resource from a cloned connection.
1336
+     * The cloned connection needs to be closed manually.
1337
+     * of the current access.
1338
+     *
1339
+     * @param string $gid
1340
+     * @return resource|\LDAP\Connection The LDAP connection
1341
+     * @throws ServerNotAvailableException
1342
+     */
1343
+    public function getNewLDAPConnection($gid) {
1344
+        $connection = clone $this->access->getConnection();
1345
+        return $connection->getConnectionResource();
1346
+    }
1347
+
1348
+    /**
1349
+     * @throws ServerNotAvailableException
1350
+     */
1351
+    public function getDisplayName(string $gid): string {
1352
+        if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1353
+            return $this->groupPluginManager->getDisplayName($gid);
1354
+        }
1355
+
1356
+        $cacheKey = 'group_getDisplayName' . $gid;
1357
+        if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1358
+            return $displayName;
1359
+        }
1360
+
1361
+        $displayName = $this->access->readAttribute(
1362
+            $this->access->groupname2dn($gid),
1363
+            $this->access->connection->ldapGroupDisplayName);
1364
+
1365
+        if (($displayName !== false) && (count($displayName) > 0)) {
1366
+            $displayName = $displayName[0];
1367
+            $this->access->connection->writeToCache($cacheKey, $displayName);
1368
+            return $displayName;
1369
+        }
1370
+
1371
+        return '';
1372
+    }
1373 1373
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/GroupPluginManager.php 1 patch
Indentation   +157 added lines, -157 removed lines patch added patch discarded remove patch
@@ -26,161 +26,161 @@
 block discarded – undo
26 26
 use OCP\GroupInterface;
27 27
 
28 28
 class GroupPluginManager {
29
-	private int $respondToActions = 0;
30
-
31
-	/** @var array<int, ?ILDAPGroupPlugin> */
32
-	private array $which = [
33
-		GroupInterface::CREATE_GROUP => null,
34
-		GroupInterface::DELETE_GROUP => null,
35
-		GroupInterface::ADD_TO_GROUP => null,
36
-		GroupInterface::REMOVE_FROM_GROUP => null,
37
-		GroupInterface::COUNT_USERS => null,
38
-		GroupInterface::GROUP_DETAILS => null
39
-	];
40
-
41
-	private bool $suppressDeletion = false;
42
-
43
-	/**
44
-	 * @return int All implemented actions
45
-	 */
46
-	public function getImplementedActions() {
47
-		return $this->respondToActions;
48
-	}
49
-
50
-	/**
51
-	 * Registers a group plugin that may implement some actions, overriding User_LDAP's group actions.
52
-	 * @param ILDAPGroupPlugin $plugin
53
-	 */
54
-	public function register(ILDAPGroupPlugin $plugin) {
55
-		$respondToActions = $plugin->respondToActions();
56
-		$this->respondToActions |= $respondToActions;
57
-
58
-		foreach ($this->which as $action => $v) {
59
-			if ((bool)($respondToActions & $action)) {
60
-				$this->which[$action] = $plugin;
61
-				\OC::$server->getLogger()->debug("Registered action ".$action." to plugin ".get_class($plugin), ['app' => 'user_ldap']);
62
-			}
63
-		}
64
-	}
65
-
66
-	/**
67
-	 * Signal if there is a registered plugin that implements some given actions
68
-	 * @param int $actions Actions defined in \OCP\GroupInterface, like GroupInterface::REMOVE_FROM_GROUP
69
-	 * @return bool
70
-	 */
71
-	public function implementsActions($actions) {
72
-		return ($actions & $this->respondToActions) == $actions;
73
-	}
74
-
75
-	/**
76
-	 * Create a group
77
-	 * @param string $gid Group Id
78
-	 * @return string | null The group DN if group creation was successful.
79
-	 * @throws \Exception
80
-	 */
81
-	public function createGroup($gid) {
82
-		$plugin = $this->which[GroupInterface::CREATE_GROUP];
83
-
84
-		if ($plugin) {
85
-			return $plugin->createGroup($gid);
86
-		}
87
-		throw new \Exception('No plugin implements createGroup in this LDAP Backend.');
88
-	}
89
-
90
-	public function canDeleteGroup(): bool {
91
-		return !$this->suppressDeletion && $this->implementsActions(GroupInterface::DELETE_GROUP);
92
-	}
93
-
94
-	/**
95
-	 * @return bool – the value before the change
96
-	 */
97
-	public function setSuppressDeletion(bool $value): bool {
98
-		$old = $this->suppressDeletion;
99
-		$this->suppressDeletion = $value;
100
-		return $old;
101
-	}
102
-
103
-	/**
104
-	 * Delete a group
105
-	 *
106
-	 * @throws \Exception
107
-	 */
108
-	public function deleteGroup(string $gid): bool {
109
-		$plugin = $this->which[GroupInterface::DELETE_GROUP];
110
-
111
-		if ($plugin) {
112
-			if ($this->suppressDeletion) {
113
-				return false;
114
-			}
115
-			return $plugin->deleteGroup($gid);
116
-		}
117
-		throw new \Exception('No plugin implements deleteGroup in this LDAP Backend.');
118
-	}
119
-
120
-	/**
121
-	 * Add a user to a group
122
-	 * @param string $uid ID of the user to add to group
123
-	 * @param string $gid ID of the group in which add the user
124
-	 * @return bool
125
-	 * @throws \Exception
126
-	 *
127
-	 * Adds a user to a group.
128
-	 */
129
-	public function addToGroup($uid, $gid) {
130
-		$plugin = $this->which[GroupInterface::ADD_TO_GROUP];
131
-
132
-		if ($plugin) {
133
-			return $plugin->addToGroup($uid, $gid);
134
-		}
135
-		throw new \Exception('No plugin implements addToGroup in this LDAP Backend.');
136
-	}
137
-
138
-	/**
139
-	 * Removes a user from a group
140
-	 * @param string $uid ID of the user to remove from group
141
-	 * @param string $gid ID of the group from which remove the user
142
-	 * @return bool
143
-	 * @throws \Exception
144
-	 *
145
-	 * removes the user from a group.
146
-	 */
147
-	public function removeFromGroup($uid, $gid) {
148
-		$plugin = $this->which[GroupInterface::REMOVE_FROM_GROUP];
149
-
150
-		if ($plugin) {
151
-			return $plugin->removeFromGroup($uid, $gid);
152
-		}
153
-		throw new \Exception('No plugin implements removeFromGroup in this LDAP Backend.');
154
-	}
155
-
156
-	/**
157
-	 * get the number of all users matching the search string in a group
158
-	 * @param string $gid ID of the group
159
-	 * @param string $search query string
160
-	 * @return int|false
161
-	 * @throws \Exception
162
-	 */
163
-	public function countUsersInGroup($gid, $search = '') {
164
-		$plugin = $this->which[GroupInterface::COUNT_USERS];
165
-
166
-		if ($plugin) {
167
-			return $plugin->countUsersInGroup($gid,$search);
168
-		}
169
-		throw new \Exception('No plugin implements countUsersInGroup in this LDAP Backend.');
170
-	}
171
-
172
-	/**
173
-	 * get an array with group details
174
-	 * @param string $gid
175
-	 * @return array|false
176
-	 * @throws \Exception
177
-	 */
178
-	public function getGroupDetails($gid) {
179
-		$plugin = $this->which[GroupInterface::GROUP_DETAILS];
180
-
181
-		if ($plugin) {
182
-			return $plugin->getGroupDetails($gid);
183
-		}
184
-		throw new \Exception('No plugin implements getGroupDetails in this LDAP Backend.');
185
-	}
29
+    private int $respondToActions = 0;
30
+
31
+    /** @var array<int, ?ILDAPGroupPlugin> */
32
+    private array $which = [
33
+        GroupInterface::CREATE_GROUP => null,
34
+        GroupInterface::DELETE_GROUP => null,
35
+        GroupInterface::ADD_TO_GROUP => null,
36
+        GroupInterface::REMOVE_FROM_GROUP => null,
37
+        GroupInterface::COUNT_USERS => null,
38
+        GroupInterface::GROUP_DETAILS => null
39
+    ];
40
+
41
+    private bool $suppressDeletion = false;
42
+
43
+    /**
44
+     * @return int All implemented actions
45
+     */
46
+    public function getImplementedActions() {
47
+        return $this->respondToActions;
48
+    }
49
+
50
+    /**
51
+     * Registers a group plugin that may implement some actions, overriding User_LDAP's group actions.
52
+     * @param ILDAPGroupPlugin $plugin
53
+     */
54
+    public function register(ILDAPGroupPlugin $plugin) {
55
+        $respondToActions = $plugin->respondToActions();
56
+        $this->respondToActions |= $respondToActions;
57
+
58
+        foreach ($this->which as $action => $v) {
59
+            if ((bool)($respondToActions & $action)) {
60
+                $this->which[$action] = $plugin;
61
+                \OC::$server->getLogger()->debug("Registered action ".$action." to plugin ".get_class($plugin), ['app' => 'user_ldap']);
62
+            }
63
+        }
64
+    }
65
+
66
+    /**
67
+     * Signal if there is a registered plugin that implements some given actions
68
+     * @param int $actions Actions defined in \OCP\GroupInterface, like GroupInterface::REMOVE_FROM_GROUP
69
+     * @return bool
70
+     */
71
+    public function implementsActions($actions) {
72
+        return ($actions & $this->respondToActions) == $actions;
73
+    }
74
+
75
+    /**
76
+     * Create a group
77
+     * @param string $gid Group Id
78
+     * @return string | null The group DN if group creation was successful.
79
+     * @throws \Exception
80
+     */
81
+    public function createGroup($gid) {
82
+        $plugin = $this->which[GroupInterface::CREATE_GROUP];
83
+
84
+        if ($plugin) {
85
+            return $plugin->createGroup($gid);
86
+        }
87
+        throw new \Exception('No plugin implements createGroup in this LDAP Backend.');
88
+    }
89
+
90
+    public function canDeleteGroup(): bool {
91
+        return !$this->suppressDeletion && $this->implementsActions(GroupInterface::DELETE_GROUP);
92
+    }
93
+
94
+    /**
95
+     * @return bool – the value before the change
96
+     */
97
+    public function setSuppressDeletion(bool $value): bool {
98
+        $old = $this->suppressDeletion;
99
+        $this->suppressDeletion = $value;
100
+        return $old;
101
+    }
102
+
103
+    /**
104
+     * Delete a group
105
+     *
106
+     * @throws \Exception
107
+     */
108
+    public function deleteGroup(string $gid): bool {
109
+        $plugin = $this->which[GroupInterface::DELETE_GROUP];
110
+
111
+        if ($plugin) {
112
+            if ($this->suppressDeletion) {
113
+                return false;
114
+            }
115
+            return $plugin->deleteGroup($gid);
116
+        }
117
+        throw new \Exception('No plugin implements deleteGroup in this LDAP Backend.');
118
+    }
119
+
120
+    /**
121
+     * Add a user to a group
122
+     * @param string $uid ID of the user to add to group
123
+     * @param string $gid ID of the group in which add the user
124
+     * @return bool
125
+     * @throws \Exception
126
+     *
127
+     * Adds a user to a group.
128
+     */
129
+    public function addToGroup($uid, $gid) {
130
+        $plugin = $this->which[GroupInterface::ADD_TO_GROUP];
131
+
132
+        if ($plugin) {
133
+            return $plugin->addToGroup($uid, $gid);
134
+        }
135
+        throw new \Exception('No plugin implements addToGroup in this LDAP Backend.');
136
+    }
137
+
138
+    /**
139
+     * Removes a user from a group
140
+     * @param string $uid ID of the user to remove from group
141
+     * @param string $gid ID of the group from which remove the user
142
+     * @return bool
143
+     * @throws \Exception
144
+     *
145
+     * removes the user from a group.
146
+     */
147
+    public function removeFromGroup($uid, $gid) {
148
+        $plugin = $this->which[GroupInterface::REMOVE_FROM_GROUP];
149
+
150
+        if ($plugin) {
151
+            return $plugin->removeFromGroup($uid, $gid);
152
+        }
153
+        throw new \Exception('No plugin implements removeFromGroup in this LDAP Backend.');
154
+    }
155
+
156
+    /**
157
+     * get the number of all users matching the search string in a group
158
+     * @param string $gid ID of the group
159
+     * @param string $search query string
160
+     * @return int|false
161
+     * @throws \Exception
162
+     */
163
+    public function countUsersInGroup($gid, $search = '') {
164
+        $plugin = $this->which[GroupInterface::COUNT_USERS];
165
+
166
+        if ($plugin) {
167
+            return $plugin->countUsersInGroup($gid,$search);
168
+        }
169
+        throw new \Exception('No plugin implements countUsersInGroup in this LDAP Backend.');
170
+    }
171
+
172
+    /**
173
+     * get an array with group details
174
+     * @param string $gid
175
+     * @return array|false
176
+     * @throws \Exception
177
+     */
178
+    public function getGroupDetails($gid) {
179
+        $plugin = $this->which[GroupInterface::GROUP_DETAILS];
180
+
181
+        if ($plugin) {
182
+            return $plugin->getGroupDetails($gid);
183
+        }
184
+        throw new \Exception('No plugin implements getGroupDetails in this LDAP Backend.');
185
+    }
186 186
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_Proxy.php 1 patch
Indentation   +247 added lines, -247 removed lines patch added patch discarded remove patch
@@ -33,277 +33,277 @@
 block discarded – undo
33 33
 use OCP\Group\Backend\INamedBackend;
34 34
 
35 35
 class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend {
36
-	private $backends = [];
37
-	private $refBackend = null;
36
+    private $backends = [];
37
+    private $refBackend = null;
38 38
 
39
-	public function __construct(Helper $helper, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) {
40
-		parent::__construct($ldap);
41
-		$serverConfigPrefixes = $helper->getServerConfigurationPrefixes(true);
42
-		foreach ($serverConfigPrefixes as $configPrefix) {
43
-			$this->backends[$configPrefix] =
44
-				new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix), $groupPluginManager);
45
-			if (is_null($this->refBackend)) {
46
-				$this->refBackend = &$this->backends[$configPrefix];
47
-			}
48
-		}
49
-	}
39
+    public function __construct(Helper $helper, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) {
40
+        parent::__construct($ldap);
41
+        $serverConfigPrefixes = $helper->getServerConfigurationPrefixes(true);
42
+        foreach ($serverConfigPrefixes as $configPrefix) {
43
+            $this->backends[$configPrefix] =
44
+                new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix), $groupPluginManager);
45
+            if (is_null($this->refBackend)) {
46
+                $this->refBackend = &$this->backends[$configPrefix];
47
+            }
48
+        }
49
+    }
50 50
 
51
-	/**
52
-	 * Tries the backends one after the other until a positive result is returned from the specified method
53
-	 *
54
-	 * @param string $id the gid connected to the request
55
-	 * @param string $method the method of the group backend that shall be called
56
-	 * @param array $parameters an array of parameters to be passed
57
-	 * @return mixed the result of the method or false
58
-	 */
59
-	protected function walkBackends($id, $method, $parameters) {
60
-		$gid = $id;
61
-		$cacheKey = $this->getGroupCacheKey($gid);
62
-		foreach ($this->backends as $configPrefix => $backend) {
63
-			if ($result = call_user_func_array([$backend, $method], $parameters)) {
64
-				if (!$this->isSingleBackend()) {
65
-					$this->writeToCache($cacheKey, $configPrefix);
66
-				}
67
-				return $result;
68
-			}
69
-		}
70
-		return false;
71
-	}
51
+    /**
52
+     * Tries the backends one after the other until a positive result is returned from the specified method
53
+     *
54
+     * @param string $id the gid connected to the request
55
+     * @param string $method the method of the group backend that shall be called
56
+     * @param array $parameters an array of parameters to be passed
57
+     * @return mixed the result of the method or false
58
+     */
59
+    protected function walkBackends($id, $method, $parameters) {
60
+        $gid = $id;
61
+        $cacheKey = $this->getGroupCacheKey($gid);
62
+        foreach ($this->backends as $configPrefix => $backend) {
63
+            if ($result = call_user_func_array([$backend, $method], $parameters)) {
64
+                if (!$this->isSingleBackend()) {
65
+                    $this->writeToCache($cacheKey, $configPrefix);
66
+                }
67
+                return $result;
68
+            }
69
+        }
70
+        return false;
71
+    }
72 72
 
73
-	/**
74
-	 * Asks the backend connected to the server that supposely takes care of the gid from the request.
75
-	 *
76
-	 * @param string $id the gid connected to the request
77
-	 * @param string $method the method of the group backend that shall be called
78
-	 * @param array $parameters an array of parameters to be passed
79
-	 * @param mixed $passOnWhen the result matches this variable
80
-	 * @return mixed the result of the method or false
81
-	 */
82
-	protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) {
83
-		$gid = $id;
84
-		$cacheKey = $this->getGroupCacheKey($gid);
85
-		$prefix = $this->getFromCache($cacheKey);
86
-		//in case the uid has been found in the past, try this stored connection first
87
-		if (!is_null($prefix)) {
88
-			if (isset($this->backends[$prefix])) {
89
-				$result = call_user_func_array([$this->backends[$prefix], $method], $parameters);
90
-				if ($result === $passOnWhen) {
91
-					//not found here, reset cache to null if group vanished
92
-					//because sometimes methods return false with a reason
93
-					$groupExists = call_user_func_array(
94
-						[$this->backends[$prefix], 'groupExists'],
95
-						[$gid]
96
-					);
97
-					if (!$groupExists) {
98
-						$this->writeToCache($cacheKey, null);
99
-					}
100
-				}
101
-				return $result;
102
-			}
103
-		}
104
-		return false;
105
-	}
73
+    /**
74
+     * Asks the backend connected to the server that supposely takes care of the gid from the request.
75
+     *
76
+     * @param string $id the gid connected to the request
77
+     * @param string $method the method of the group backend that shall be called
78
+     * @param array $parameters an array of parameters to be passed
79
+     * @param mixed $passOnWhen the result matches this variable
80
+     * @return mixed the result of the method or false
81
+     */
82
+    protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) {
83
+        $gid = $id;
84
+        $cacheKey = $this->getGroupCacheKey($gid);
85
+        $prefix = $this->getFromCache($cacheKey);
86
+        //in case the uid has been found in the past, try this stored connection first
87
+        if (!is_null($prefix)) {
88
+            if (isset($this->backends[$prefix])) {
89
+                $result = call_user_func_array([$this->backends[$prefix], $method], $parameters);
90
+                if ($result === $passOnWhen) {
91
+                    //not found here, reset cache to null if group vanished
92
+                    //because sometimes methods return false with a reason
93
+                    $groupExists = call_user_func_array(
94
+                        [$this->backends[$prefix], 'groupExists'],
95
+                        [$gid]
96
+                    );
97
+                    if (!$groupExists) {
98
+                        $this->writeToCache($cacheKey, null);
99
+                    }
100
+                }
101
+                return $result;
102
+            }
103
+        }
104
+        return false;
105
+    }
106 106
 
107
-	protected function activeBackends(): int {
108
-		return count($this->backends);
109
-	}
107
+    protected function activeBackends(): int {
108
+        return count($this->backends);
109
+    }
110 110
 
111
-	/**
112
-	 * is user in group?
113
-	 *
114
-	 * @param string $uid uid of the user
115
-	 * @param string $gid gid of the group
116
-	 * @return bool
117
-	 *
118
-	 * Checks whether the user is member of a group or not.
119
-	 */
120
-	public function inGroup($uid, $gid) {
121
-		return $this->handleRequest($gid, 'inGroup', [$uid, $gid]);
122
-	}
111
+    /**
112
+     * is user in group?
113
+     *
114
+     * @param string $uid uid of the user
115
+     * @param string $gid gid of the group
116
+     * @return bool
117
+     *
118
+     * Checks whether the user is member of a group or not.
119
+     */
120
+    public function inGroup($uid, $gid) {
121
+        return $this->handleRequest($gid, 'inGroup', [$uid, $gid]);
122
+    }
123 123
 
124
-	/**
125
-	 * Get all groups a user belongs to
126
-	 *
127
-	 * @param string $uid Name of the user
128
-	 * @return string[] with group names
129
-	 *
130
-	 * This function fetches all groups a user belongs to. It does not check
131
-	 * if the user exists at all.
132
-	 */
133
-	public function getUserGroups($uid) {
134
-		$groups = [];
124
+    /**
125
+     * Get all groups a user belongs to
126
+     *
127
+     * @param string $uid Name of the user
128
+     * @return string[] with group names
129
+     *
130
+     * This function fetches all groups a user belongs to. It does not check
131
+     * if the user exists at all.
132
+     */
133
+    public function getUserGroups($uid) {
134
+        $groups = [];
135 135
 
136
-		foreach ($this->backends as $backend) {
137
-			$backendGroups = $backend->getUserGroups($uid);
138
-			if (is_array($backendGroups)) {
139
-				$groups = array_merge($groups, $backendGroups);
140
-			}
141
-		}
136
+        foreach ($this->backends as $backend) {
137
+            $backendGroups = $backend->getUserGroups($uid);
138
+            if (is_array($backendGroups)) {
139
+                $groups = array_merge($groups, $backendGroups);
140
+            }
141
+        }
142 142
 
143
-		return $groups;
144
-	}
143
+        return $groups;
144
+    }
145 145
 
146
-	/**
147
-	 * get a list of all users in a group
148
-	 *
149
-	 * @return string[] with user ids
150
-	 */
151
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
152
-		$users = [];
146
+    /**
147
+     * get a list of all users in a group
148
+     *
149
+     * @return string[] with user ids
150
+     */
151
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
152
+        $users = [];
153 153
 
154
-		foreach ($this->backends as $backend) {
155
-			$backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset);
156
-			if (is_array($backendUsers)) {
157
-				$users = array_merge($users, $backendUsers);
158
-			}
159
-		}
154
+        foreach ($this->backends as $backend) {
155
+            $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset);
156
+            if (is_array($backendUsers)) {
157
+                $users = array_merge($users, $backendUsers);
158
+            }
159
+        }
160 160
 
161
-		return $users;
162
-	}
161
+        return $users;
162
+    }
163 163
 
164
-	/**
165
-	 * @param string $gid
166
-	 * @return bool
167
-	 */
168
-	public function createGroup($gid) {
169
-		return $this->handleRequest(
170
-			$gid, 'createGroup', [$gid]);
171
-	}
164
+    /**
165
+     * @param string $gid
166
+     * @return bool
167
+     */
168
+    public function createGroup($gid) {
169
+        return $this->handleRequest(
170
+            $gid, 'createGroup', [$gid]);
171
+    }
172 172
 
173
-	/**
174
-	 * delete a group
175
-	 */
176
-	public function deleteGroup(string $gid): bool {
177
-		return $this->handleRequest(
178
-			$gid, 'deleteGroup', [$gid]);
179
-	}
173
+    /**
174
+     * delete a group
175
+     */
176
+    public function deleteGroup(string $gid): bool {
177
+        return $this->handleRequest(
178
+            $gid, 'deleteGroup', [$gid]);
179
+    }
180 180
 
181
-	/**
182
-	 * Add a user to a group
183
-	 *
184
-	 * @param string $uid Name of the user to add to group
185
-	 * @param string $gid Name of the group in which add the user
186
-	 * @return bool
187
-	 *
188
-	 * Adds a user to a group.
189
-	 */
190
-	public function addToGroup($uid, $gid) {
191
-		return $this->handleRequest(
192
-			$gid, 'addToGroup', [$uid, $gid]);
193
-	}
181
+    /**
182
+     * Add a user to a group
183
+     *
184
+     * @param string $uid Name of the user to add to group
185
+     * @param string $gid Name of the group in which add the user
186
+     * @return bool
187
+     *
188
+     * Adds a user to a group.
189
+     */
190
+    public function addToGroup($uid, $gid) {
191
+        return $this->handleRequest(
192
+            $gid, 'addToGroup', [$uid, $gid]);
193
+    }
194 194
 
195
-	/**
196
-	 * Removes a user from a group
197
-	 *
198
-	 * @param string $uid Name of the user to remove from group
199
-	 * @param string $gid Name of the group from which remove the user
200
-	 * @return bool
201
-	 *
202
-	 * removes the user from a group.
203
-	 */
204
-	public function removeFromGroup($uid, $gid) {
205
-		return $this->handleRequest(
206
-			$gid, 'removeFromGroup', [$uid, $gid]);
207
-	}
195
+    /**
196
+     * Removes a user from a group
197
+     *
198
+     * @param string $uid Name of the user to remove from group
199
+     * @param string $gid Name of the group from which remove the user
200
+     * @return bool
201
+     *
202
+     * removes the user from a group.
203
+     */
204
+    public function removeFromGroup($uid, $gid) {
205
+        return $this->handleRequest(
206
+            $gid, 'removeFromGroup', [$uid, $gid]);
207
+    }
208 208
 
209
-	/**
210
-	 * returns the number of users in a group, who match the search term
211
-	 *
212
-	 * @param string $gid the internal group name
213
-	 * @param string $search optional, a search string
214
-	 * @return int|bool
215
-	 */
216
-	public function countUsersInGroup($gid, $search = '') {
217
-		return $this->handleRequest(
218
-			$gid, 'countUsersInGroup', [$gid, $search]);
219
-	}
209
+    /**
210
+     * returns the number of users in a group, who match the search term
211
+     *
212
+     * @param string $gid the internal group name
213
+     * @param string $search optional, a search string
214
+     * @return int|bool
215
+     */
216
+    public function countUsersInGroup($gid, $search = '') {
217
+        return $this->handleRequest(
218
+            $gid, 'countUsersInGroup', [$gid, $search]);
219
+    }
220 220
 
221
-	/**
222
-	 * get an array with group details
223
-	 *
224
-	 * @param string $gid
225
-	 * @return array|false
226
-	 */
227
-	public function getGroupDetails($gid) {
228
-		return $this->handleRequest(
229
-			$gid, 'getGroupDetails', [$gid]);
230
-	}
221
+    /**
222
+     * get an array with group details
223
+     *
224
+     * @param string $gid
225
+     * @return array|false
226
+     */
227
+    public function getGroupDetails($gid) {
228
+        return $this->handleRequest(
229
+            $gid, 'getGroupDetails', [$gid]);
230
+    }
231 231
 
232
-	/**
233
-	 * get a list of all groups
234
-	 *
235
-	 * @return string[] with group names
236
-	 *
237
-	 * Returns a list with all groups
238
-	 */
239
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
240
-		$groups = [];
232
+    /**
233
+     * get a list of all groups
234
+     *
235
+     * @return string[] with group names
236
+     *
237
+     * Returns a list with all groups
238
+     */
239
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
240
+        $groups = [];
241 241
 
242
-		foreach ($this->backends as $backend) {
243
-			$backendGroups = $backend->getGroups($search, $limit, $offset);
244
-			if (is_array($backendGroups)) {
245
-				$groups = array_merge($groups, $backendGroups);
246
-			}
247
-		}
242
+        foreach ($this->backends as $backend) {
243
+            $backendGroups = $backend->getGroups($search, $limit, $offset);
244
+            if (is_array($backendGroups)) {
245
+                $groups = array_merge($groups, $backendGroups);
246
+            }
247
+        }
248 248
 
249
-		return $groups;
250
-	}
249
+        return $groups;
250
+    }
251 251
 
252
-	/**
253
-	 * check if a group exists
254
-	 *
255
-	 * @param string $gid
256
-	 * @return bool
257
-	 */
258
-	public function groupExists($gid) {
259
-		return $this->handleRequest($gid, 'groupExists', [$gid]);
260
-	}
252
+    /**
253
+     * check if a group exists
254
+     *
255
+     * @param string $gid
256
+     * @return bool
257
+     */
258
+    public function groupExists($gid) {
259
+        return $this->handleRequest($gid, 'groupExists', [$gid]);
260
+    }
261 261
 
262
-	/**
263
-	 * Check if backend implements actions
264
-	 *
265
-	 * @param int $actions bitwise-or'ed actions
266
-	 * @return boolean
267
-	 *
268
-	 * Returns the supported actions as int to be
269
-	 * compared with \OCP\GroupInterface::CREATE_GROUP etc.
270
-	 */
271
-	public function implementsActions($actions) {
272
-		//it's the same across all our user backends obviously
273
-		return $this->refBackend->implementsActions($actions);
274
-	}
262
+    /**
263
+     * Check if backend implements actions
264
+     *
265
+     * @param int $actions bitwise-or'ed actions
266
+     * @return boolean
267
+     *
268
+     * Returns the supported actions as int to be
269
+     * compared with \OCP\GroupInterface::CREATE_GROUP etc.
270
+     */
271
+    public function implementsActions($actions) {
272
+        //it's the same across all our user backends obviously
273
+        return $this->refBackend->implementsActions($actions);
274
+    }
275 275
 
276
-	/**
277
-	 * Return access for LDAP interaction.
278
-	 *
279
-	 * @param string $gid
280
-	 * @return Access instance of Access for LDAP interaction
281
-	 */
282
-	public function getLDAPAccess($gid) {
283
-		return $this->handleRequest($gid, 'getLDAPAccess', [$gid]);
284
-	}
276
+    /**
277
+     * Return access for LDAP interaction.
278
+     *
279
+     * @param string $gid
280
+     * @return Access instance of Access for LDAP interaction
281
+     */
282
+    public function getLDAPAccess($gid) {
283
+        return $this->handleRequest($gid, 'getLDAPAccess', [$gid]);
284
+    }
285 285
 
286
-	/**
287
-	 * Return a new LDAP connection for the specified group.
288
-	 * The connection needs to be closed manually.
289
-	 *
290
-	 * @param string $gid
291
-	 * @return resource|\LDAP\Connection The LDAP connection
292
-	 */
293
-	public function getNewLDAPConnection($gid) {
294
-		return $this->handleRequest($gid, 'getNewLDAPConnection', [$gid]);
295
-	}
286
+    /**
287
+     * Return a new LDAP connection for the specified group.
288
+     * The connection needs to be closed manually.
289
+     *
290
+     * @param string $gid
291
+     * @return resource|\LDAP\Connection The LDAP connection
292
+     */
293
+    public function getNewLDAPConnection($gid) {
294
+        return $this->handleRequest($gid, 'getNewLDAPConnection', [$gid]);
295
+    }
296 296
 
297
-	public function getDisplayName(string $gid): string {
298
-		return $this->handleRequest($gid, 'getDisplayName', [$gid]);
299
-	}
297
+    public function getDisplayName(string $gid): string {
298
+        return $this->handleRequest($gid, 'getDisplayName', [$gid]);
299
+    }
300 300
 
301
-	/**
302
-	 * Backend name to be shown in group management
303
-	 * @return string the name of the backend to be shown
304
-	 * @since 22.0.0
305
-	 */
306
-	public function getBackendName(): string {
307
-		return 'LDAP';
308
-	}
301
+    /**
302
+     * Backend name to be shown in group management
303
+     * @return string the name of the backend to be shown
304
+     * @since 22.0.0
305
+     */
306
+    public function getBackendName(): string {
307
+        return 'LDAP';
308
+    }
309 309
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/UserPluginManager.php 1 patch
Indentation   +192 added lines, -192 removed lines patch added patch discarded remove patch
@@ -28,196 +28,196 @@
 block discarded – undo
28 28
 use OC\User\Backend;
29 29
 
30 30
 class UserPluginManager {
31
-	private int $respondToActions = 0;
32
-
33
-	private array $which = [
34
-		Backend::CREATE_USER => null,
35
-		Backend::SET_PASSWORD => null,
36
-		Backend::GET_HOME => null,
37
-		Backend::GET_DISPLAYNAME => null,
38
-		Backend::SET_DISPLAYNAME => null,
39
-		Backend::PROVIDE_AVATAR => null,
40
-		Backend::COUNT_USERS => null,
41
-		'deleteUser' => null
42
-	];
43
-
44
-	private bool $suppressDeletion = false;
45
-
46
-	/**
47
-	 * @return int All implemented actions, except for 'deleteUser'
48
-	 */
49
-	public function getImplementedActions() {
50
-		return $this->respondToActions;
51
-	}
52
-
53
-	/**
54
-	 * Registers a user plugin that may implement some actions, overriding User_LDAP's user actions.
55
-	 *
56
-	 * @param ILDAPUserPlugin $plugin
57
-	 */
58
-	public function register(ILDAPUserPlugin $plugin) {
59
-		$respondToActions = $plugin->respondToActions();
60
-		$this->respondToActions |= $respondToActions;
61
-
62
-		foreach ($this->which as $action => $v) {
63
-			if (is_int($action) && (bool)($respondToActions & $action)) {
64
-				$this->which[$action] = $plugin;
65
-				\OC::$server->getLogger()->debug("Registered action ".$action." to plugin ".get_class($plugin), ['app' => 'user_ldap']);
66
-			}
67
-		}
68
-		if (method_exists($plugin,'deleteUser')) {
69
-			$this->which['deleteUser'] = $plugin;
70
-			\OC::$server->getLogger()->debug("Registered action deleteUser to plugin ".get_class($plugin), ['app' => 'user_ldap']);
71
-		}
72
-	}
73
-
74
-	/**
75
-	 * Signal if there is a registered plugin that implements some given actions
76
-	 * @param int $actions Actions defined in \OC\User\Backend, like Backend::CREATE_USER
77
-	 * @return bool
78
-	 */
79
-	public function implementsActions($actions) {
80
-		return ($actions & $this->respondToActions) == $actions;
81
-	}
82
-
83
-	/**
84
-	 * Create a new user in LDAP Backend
85
-	 *
86
-	 * @param string $username The username of the user to create
87
-	 * @param string $password The password of the new user
88
-	 * @return string | false The user DN if user creation was successful.
89
-	 * @throws \Exception
90
-	 */
91
-	public function createUser($username, $password) {
92
-		$plugin = $this->which[Backend::CREATE_USER];
93
-
94
-		if ($plugin) {
95
-			return $plugin->createUser($username,$password);
96
-		}
97
-		throw new \Exception('No plugin implements createUser in this LDAP Backend.');
98
-	}
99
-
100
-	/**
101
-	 * Change the password of a user*
102
-	 * @param string $uid The username
103
-	 * @param string $password The new password
104
-	 * @return bool
105
-	 * @throws \Exception
106
-	 */
107
-	public function setPassword($uid, $password) {
108
-		$plugin = $this->which[Backend::SET_PASSWORD];
109
-
110
-		if ($plugin) {
111
-			return $plugin->setPassword($uid,$password);
112
-		}
113
-		throw new \Exception('No plugin implements setPassword in this LDAP Backend.');
114
-	}
115
-
116
-	/**
117
-	 * checks whether the user is allowed to change his avatar in Nextcloud
118
-	 * @param string $uid the Nextcloud user name
119
-	 * @return boolean either the user can or cannot
120
-	 * @throws \Exception
121
-	 */
122
-	public function canChangeAvatar($uid) {
123
-		$plugin = $this->which[Backend::PROVIDE_AVATAR];
124
-
125
-		if ($plugin) {
126
-			return $plugin->canChangeAvatar($uid);
127
-		}
128
-		throw new \Exception('No plugin implements canChangeAvatar in this LDAP Backend.');
129
-	}
130
-
131
-	/**
132
-	 * Get the user's home directory
133
-	 * @param string $uid the username
134
-	 * @return boolean
135
-	 * @throws \Exception
136
-	 */
137
-	public function getHome($uid) {
138
-		$plugin = $this->which[Backend::GET_HOME];
139
-
140
-		if ($plugin) {
141
-			return $plugin->getHome($uid);
142
-		}
143
-		throw new \Exception('No plugin implements getHome in this LDAP Backend.');
144
-	}
145
-
146
-	/**
147
-	 * Get display name of the user
148
-	 * @param string $uid user ID of the user
149
-	 * @return string display name
150
-	 * @throws \Exception
151
-	 */
152
-	public function getDisplayName($uid) {
153
-		$plugin = $this->which[Backend::GET_DISPLAYNAME];
154
-
155
-		if ($plugin) {
156
-			return $plugin->getDisplayName($uid);
157
-		}
158
-		throw new \Exception('No plugin implements getDisplayName in this LDAP Backend.');
159
-	}
160
-
161
-	/**
162
-	 * Set display name of the user
163
-	 * @param string $uid user ID of the user
164
-	 * @param string $displayName new user's display name
165
-	 * @return string display name
166
-	 * @throws \Exception
167
-	 */
168
-	public function setDisplayName($uid, $displayName) {
169
-		$plugin = $this->which[Backend::SET_DISPLAYNAME];
170
-
171
-		if ($plugin) {
172
-			return $plugin->setDisplayName($uid, $displayName);
173
-		}
174
-		throw new \Exception('No plugin implements setDisplayName in this LDAP Backend.');
175
-	}
176
-
177
-	/**
178
-	 * Count the number of users
179
-	 * @return int|bool
180
-	 * @throws \Exception
181
-	 */
182
-	public function countUsers() {
183
-		$plugin = $this->which[Backend::COUNT_USERS];
184
-
185
-		if ($plugin) {
186
-			return $plugin->countUsers();
187
-		}
188
-		throw new \Exception('No plugin implements countUsers in this LDAP Backend.');
189
-	}
190
-
191
-	/**
192
-	 * @return bool
193
-	 */
194
-	public function canDeleteUser() {
195
-		return !$this->suppressDeletion && $this->which['deleteUser'] !== null;
196
-	}
197
-
198
-	/**
199
-	 * @param $uid
200
-	 * @return bool
201
-	 * @throws \Exception
202
-	 */
203
-	public function deleteUser($uid) {
204
-		$plugin = $this->which['deleteUser'];
205
-		if ($plugin) {
206
-			if ($this->suppressDeletion) {
207
-				return false;
208
-			}
209
-			return $plugin->deleteUser($uid);
210
-		}
211
-		throw new \Exception('No plugin implements deleteUser in this LDAP Backend.');
212
-	}
213
-
214
-	/**
215
-	 * @param bool $value
216
-	 * @return bool – the value before the change
217
-	 */
218
-	public function setSuppressDeletion(bool $value): bool {
219
-		$old = $this->suppressDeletion;
220
-		$this->suppressDeletion = $value;
221
-		return $old;
222
-	}
31
+    private int $respondToActions = 0;
32
+
33
+    private array $which = [
34
+        Backend::CREATE_USER => null,
35
+        Backend::SET_PASSWORD => null,
36
+        Backend::GET_HOME => null,
37
+        Backend::GET_DISPLAYNAME => null,
38
+        Backend::SET_DISPLAYNAME => null,
39
+        Backend::PROVIDE_AVATAR => null,
40
+        Backend::COUNT_USERS => null,
41
+        'deleteUser' => null
42
+    ];
43
+
44
+    private bool $suppressDeletion = false;
45
+
46
+    /**
47
+     * @return int All implemented actions, except for 'deleteUser'
48
+     */
49
+    public function getImplementedActions() {
50
+        return $this->respondToActions;
51
+    }
52
+
53
+    /**
54
+     * Registers a user plugin that may implement some actions, overriding User_LDAP's user actions.
55
+     *
56
+     * @param ILDAPUserPlugin $plugin
57
+     */
58
+    public function register(ILDAPUserPlugin $plugin) {
59
+        $respondToActions = $plugin->respondToActions();
60
+        $this->respondToActions |= $respondToActions;
61
+
62
+        foreach ($this->which as $action => $v) {
63
+            if (is_int($action) && (bool)($respondToActions & $action)) {
64
+                $this->which[$action] = $plugin;
65
+                \OC::$server->getLogger()->debug("Registered action ".$action." to plugin ".get_class($plugin), ['app' => 'user_ldap']);
66
+            }
67
+        }
68
+        if (method_exists($plugin,'deleteUser')) {
69
+            $this->which['deleteUser'] = $plugin;
70
+            \OC::$server->getLogger()->debug("Registered action deleteUser to plugin ".get_class($plugin), ['app' => 'user_ldap']);
71
+        }
72
+    }
73
+
74
+    /**
75
+     * Signal if there is a registered plugin that implements some given actions
76
+     * @param int $actions Actions defined in \OC\User\Backend, like Backend::CREATE_USER
77
+     * @return bool
78
+     */
79
+    public function implementsActions($actions) {
80
+        return ($actions & $this->respondToActions) == $actions;
81
+    }
82
+
83
+    /**
84
+     * Create a new user in LDAP Backend
85
+     *
86
+     * @param string $username The username of the user to create
87
+     * @param string $password The password of the new user
88
+     * @return string | false The user DN if user creation was successful.
89
+     * @throws \Exception
90
+     */
91
+    public function createUser($username, $password) {
92
+        $plugin = $this->which[Backend::CREATE_USER];
93
+
94
+        if ($plugin) {
95
+            return $plugin->createUser($username,$password);
96
+        }
97
+        throw new \Exception('No plugin implements createUser in this LDAP Backend.');
98
+    }
99
+
100
+    /**
101
+     * Change the password of a user*
102
+     * @param string $uid The username
103
+     * @param string $password The new password
104
+     * @return bool
105
+     * @throws \Exception
106
+     */
107
+    public function setPassword($uid, $password) {
108
+        $plugin = $this->which[Backend::SET_PASSWORD];
109
+
110
+        if ($plugin) {
111
+            return $plugin->setPassword($uid,$password);
112
+        }
113
+        throw new \Exception('No plugin implements setPassword in this LDAP Backend.');
114
+    }
115
+
116
+    /**
117
+     * checks whether the user is allowed to change his avatar in Nextcloud
118
+     * @param string $uid the Nextcloud user name
119
+     * @return boolean either the user can or cannot
120
+     * @throws \Exception
121
+     */
122
+    public function canChangeAvatar($uid) {
123
+        $plugin = $this->which[Backend::PROVIDE_AVATAR];
124
+
125
+        if ($plugin) {
126
+            return $plugin->canChangeAvatar($uid);
127
+        }
128
+        throw new \Exception('No plugin implements canChangeAvatar in this LDAP Backend.');
129
+    }
130
+
131
+    /**
132
+     * Get the user's home directory
133
+     * @param string $uid the username
134
+     * @return boolean
135
+     * @throws \Exception
136
+     */
137
+    public function getHome($uid) {
138
+        $plugin = $this->which[Backend::GET_HOME];
139
+
140
+        if ($plugin) {
141
+            return $plugin->getHome($uid);
142
+        }
143
+        throw new \Exception('No plugin implements getHome in this LDAP Backend.');
144
+    }
145
+
146
+    /**
147
+     * Get display name of the user
148
+     * @param string $uid user ID of the user
149
+     * @return string display name
150
+     * @throws \Exception
151
+     */
152
+    public function getDisplayName($uid) {
153
+        $plugin = $this->which[Backend::GET_DISPLAYNAME];
154
+
155
+        if ($plugin) {
156
+            return $plugin->getDisplayName($uid);
157
+        }
158
+        throw new \Exception('No plugin implements getDisplayName in this LDAP Backend.');
159
+    }
160
+
161
+    /**
162
+     * Set display name of the user
163
+     * @param string $uid user ID of the user
164
+     * @param string $displayName new user's display name
165
+     * @return string display name
166
+     * @throws \Exception
167
+     */
168
+    public function setDisplayName($uid, $displayName) {
169
+        $plugin = $this->which[Backend::SET_DISPLAYNAME];
170
+
171
+        if ($plugin) {
172
+            return $plugin->setDisplayName($uid, $displayName);
173
+        }
174
+        throw new \Exception('No plugin implements setDisplayName in this LDAP Backend.');
175
+    }
176
+
177
+    /**
178
+     * Count the number of users
179
+     * @return int|bool
180
+     * @throws \Exception
181
+     */
182
+    public function countUsers() {
183
+        $plugin = $this->which[Backend::COUNT_USERS];
184
+
185
+        if ($plugin) {
186
+            return $plugin->countUsers();
187
+        }
188
+        throw new \Exception('No plugin implements countUsers in this LDAP Backend.');
189
+    }
190
+
191
+    /**
192
+     * @return bool
193
+     */
194
+    public function canDeleteUser() {
195
+        return !$this->suppressDeletion && $this->which['deleteUser'] !== null;
196
+    }
197
+
198
+    /**
199
+     * @param $uid
200
+     * @return bool
201
+     * @throws \Exception
202
+     */
203
+    public function deleteUser($uid) {
204
+        $plugin = $this->which['deleteUser'];
205
+        if ($plugin) {
206
+            if ($this->suppressDeletion) {
207
+                return false;
208
+            }
209
+            return $plugin->deleteUser($uid);
210
+        }
211
+        throw new \Exception('No plugin implements deleteUser in this LDAP Backend.');
212
+    }
213
+
214
+    /**
215
+     * @param bool $value
216
+     * @return bool – the value before the change
217
+     */
218
+    public function setSuppressDeletion(bool $value): bool {
219
+        $old = $this->suppressDeletion;
220
+        $this->suppressDeletion = $value;
221
+        return $old;
222
+    }
223 223
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Command/ResetGroup.php 1 patch
Indentation   +68 added lines, -68 removed lines patch added patch discarded remove patch
@@ -36,76 +36,76 @@
 block discarded – undo
36 36
 use Symfony\Component\Console\Question\Question;
37 37
 
38 38
 class ResetGroup extends Command {
39
-	private IGroupManager $groupManager;
40
-	private GroupPluginManager $pluginManager;
41
-	private Group_Proxy $backend;
39
+    private IGroupManager $groupManager;
40
+    private GroupPluginManager $pluginManager;
41
+    private Group_Proxy $backend;
42 42
 
43
-	public function __construct(
44
-		IGroupManager $groupManager,
45
-		GroupPluginManager $pluginManager,
46
-		Group_Proxy $backend
47
-	) {
48
-		$this->groupManager = $groupManager;
49
-		$this->pluginManager = $pluginManager;
50
-		$this->backend = $backend;
51
-		parent::__construct();
52
-	}
43
+    public function __construct(
44
+        IGroupManager $groupManager,
45
+        GroupPluginManager $pluginManager,
46
+        Group_Proxy $backend
47
+    ) {
48
+        $this->groupManager = $groupManager;
49
+        $this->pluginManager = $pluginManager;
50
+        $this->backend = $backend;
51
+        parent::__construct();
52
+    }
53 53
 
54
-	protected function configure(): void {
55
-		$this
56
-			->setName('ldap:reset-group')
57
-			->setDescription('deletes an LDAP group independent of the group state in the LDAP')
58
-			->addArgument(
59
-				'gid',
60
-				InputArgument::REQUIRED,
61
-				'the group name as used in Nextcloud'
62
-			)
63
-			->addOption(
64
-				'yes',
65
-				'y',
66
-				InputOption::VALUE_NONE,
67
-				'do not ask for confirmation'
68
-			);
69
-	}
54
+    protected function configure(): void {
55
+        $this
56
+            ->setName('ldap:reset-group')
57
+            ->setDescription('deletes an LDAP group independent of the group state in the LDAP')
58
+            ->addArgument(
59
+                'gid',
60
+                InputArgument::REQUIRED,
61
+                'the group name as used in Nextcloud'
62
+            )
63
+            ->addOption(
64
+                'yes',
65
+                'y',
66
+                InputOption::VALUE_NONE,
67
+                'do not ask for confirmation'
68
+            );
69
+    }
70 70
 
71
-	protected function execute(InputInterface $input, OutputInterface $output): int {
72
-		try {
73
-			$gid = $input->getArgument('gid');
74
-			$group = $this->groupManager->get($gid);
75
-			if (!$group instanceof IGroup) {
76
-				throw new \Exception('Group not found');
77
-			}
78
-			$backends = $group->getBackendNames();
79
-			if (!in_array('LDAP', $backends)) {
80
-				throw new \Exception('The given group is not a recognized LDAP group.');
81
-			}
82
-			if ($input->getOption('yes') === false) {
83
-				/** @var QuestionHelper $helper */
84
-				$helper = $this->getHelper('question');
85
-				$q = new Question('Delete all local data of this group (y|N)? ');
86
-				$input->setOption('yes', $helper->ask($input, $output, $q) === 'y');
87
-			}
88
-			if ($input->getOption('yes') !== true) {
89
-				throw new \Exception('Reset cancelled by operator');
90
-			}
71
+    protected function execute(InputInterface $input, OutputInterface $output): int {
72
+        try {
73
+            $gid = $input->getArgument('gid');
74
+            $group = $this->groupManager->get($gid);
75
+            if (!$group instanceof IGroup) {
76
+                throw new \Exception('Group not found');
77
+            }
78
+            $backends = $group->getBackendNames();
79
+            if (!in_array('LDAP', $backends)) {
80
+                throw new \Exception('The given group is not a recognized LDAP group.');
81
+            }
82
+            if ($input->getOption('yes') === false) {
83
+                /** @var QuestionHelper $helper */
84
+                $helper = $this->getHelper('question');
85
+                $q = new Question('Delete all local data of this group (y|N)? ');
86
+                $input->setOption('yes', $helper->ask($input, $output, $q) === 'y');
87
+            }
88
+            if ($input->getOption('yes') !== true) {
89
+                throw new \Exception('Reset cancelled by operator');
90
+            }
91 91
 
92
-			// Disable real deletion if a plugin supports it
93
-			$pluginManagerSuppressed = $this->pluginManager->setSuppressDeletion(true);
94
-			// Bypass groupExists test to force mapping deletion
95
-			$this->backend->getLDAPAccess($gid)->connection->writeToCache('groupExists' . $gid, false);
96
-			echo "calling delete $gid\n";
97
-			if ($group->delete()) {
98
-				$this->pluginManager->setSuppressDeletion($pluginManagerSuppressed);
99
-				return 0;
100
-			}
101
-		} catch (\Throwable $e) {
102
-			if (isset($pluginManagerSuppressed)) {
103
-				$this->pluginManager->setSuppressDeletion($pluginManagerSuppressed);
104
-			}
105
-			$output->writeln('<error>' . $e->getMessage() . '</error>');
106
-			return 1;
107
-		}
108
-		$output->writeln('<error>Error while resetting group</error>');
109
-		return 2;
110
-	}
92
+            // Disable real deletion if a plugin supports it
93
+            $pluginManagerSuppressed = $this->pluginManager->setSuppressDeletion(true);
94
+            // Bypass groupExists test to force mapping deletion
95
+            $this->backend->getLDAPAccess($gid)->connection->writeToCache('groupExists' . $gid, false);
96
+            echo "calling delete $gid\n";
97
+            if ($group->delete()) {
98
+                $this->pluginManager->setSuppressDeletion($pluginManagerSuppressed);
99
+                return 0;
100
+            }
101
+        } catch (\Throwable $e) {
102
+            if (isset($pluginManagerSuppressed)) {
103
+                $this->pluginManager->setSuppressDeletion($pluginManagerSuppressed);
104
+            }
105
+            $output->writeln('<error>' . $e->getMessage() . '</error>');
106
+            return 1;
107
+        }
108
+        $output->writeln('<error>Error while resetting group</error>');
109
+        return 2;
110
+    }
111 111
 }
Please login to merge, or discard this patch.
core/Command/Group/Delete.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -33,45 +33,45 @@
 block discarded – undo
33 33
 use Symfony\Component\Console\Output\OutputInterface;
34 34
 
35 35
 class Delete extends Base {
36
-	/** @var IGroupManager */
37
-	protected $groupManager;
36
+    /** @var IGroupManager */
37
+    protected $groupManager;
38 38
 
39
-	/**
40
-	 * @param IGroupManager $groupManager
41
-	 */
42
-	public function __construct(IGroupManager $groupManager) {
43
-		$this->groupManager = $groupManager;
44
-		parent::__construct();
45
-	}
39
+    /**
40
+     * @param IGroupManager $groupManager
41
+     */
42
+    public function __construct(IGroupManager $groupManager) {
43
+        $this->groupManager = $groupManager;
44
+        parent::__construct();
45
+    }
46 46
 
47
-	protected function configure() {
48
-		$this
49
-			->setName('group:delete')
50
-			->setDescription('Remove a group')
51
-			->addArgument(
52
-				'groupid',
53
-				InputArgument::REQUIRED,
54
-				'Group name'
55
-			);
56
-	}
47
+    protected function configure() {
48
+        $this
49
+            ->setName('group:delete')
50
+            ->setDescription('Remove a group')
51
+            ->addArgument(
52
+                'groupid',
53
+                InputArgument::REQUIRED,
54
+                'Group name'
55
+            );
56
+    }
57 57
 
58
-	protected function execute(InputInterface $input, OutputInterface $output): int {
59
-		$gid = $input->getArgument('groupid');
60
-		if ($gid === 'admin') {
61
-			$output->writeln('<error>Group "' . $gid . '" could not be deleted.</error>');
62
-			return 1;
63
-		}
64
-		if (!$this->groupManager->groupExists($gid)) {
65
-			$output->writeln('<error>Group "' . $gid . '" does not exist.</error>');
66
-			return 1;
67
-		}
68
-		$group = $this->groupManager->get($gid);
69
-		if ($group->delete()) {
70
-			$output->writeln('Group "' . $gid . '" was removed');
71
-		} else {
72
-			$output->writeln('<error>Group "' . $gid . '" could not be deleted. Please check the logs.</error>');
73
-			return 1;
74
-		}
75
-		return 0;
76
-	}
58
+    protected function execute(InputInterface $input, OutputInterface $output): int {
59
+        $gid = $input->getArgument('groupid');
60
+        if ($gid === 'admin') {
61
+            $output->writeln('<error>Group "' . $gid . '" could not be deleted.</error>');
62
+            return 1;
63
+        }
64
+        if (!$this->groupManager->groupExists($gid)) {
65
+            $output->writeln('<error>Group "' . $gid . '" does not exist.</error>');
66
+            return 1;
67
+        }
68
+        $group = $this->groupManager->get($gid);
69
+        if ($group->delete()) {
70
+            $output->writeln('Group "' . $gid . '" was removed');
71
+        } else {
72
+            $output->writeln('<error>Group "' . $gid . '" could not be deleted. Please check the logs.</error>');
73
+            return 1;
74
+        }
75
+        return 0;
76
+    }
77 77
 }
Please login to merge, or discard this patch.