Passed
Push — master ( a3ca6b...8f59ff )
by Blizzz
17:37 queued 12s
created
apps/user_ldap/lib/User_LDAP.php 1 patch
Indentation   +615 added lines, -615 removed lines patch added patch discarded remove patch
@@ -54,619 +54,619 @@
 block discarded – undo
54 54
 use Psr\Log\LoggerInterface;
55 55
 
56 56
 class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, IUserLDAP, ICountUsersBackend, ICountMappedUsersBackend {
57
-	/** @var \OCP\IConfig */
58
-	protected $ocConfig;
59
-
60
-	/** @var INotificationManager */
61
-	protected $notificationManager;
62
-
63
-	/** @var UserPluginManager */
64
-	protected $userPluginManager;
65
-
66
-	/** @var LoggerInterface */
67
-	protected $logger;
68
-
69
-	/**
70
-	 * @param Access $access
71
-	 * @param \OCP\IConfig $ocConfig
72
-	 * @param \OCP\Notification\IManager $notificationManager
73
-	 * @param IUserSession $userSession
74
-	 */
75
-	public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession, UserPluginManager $userPluginManager) {
76
-		parent::__construct($access);
77
-		$this->ocConfig = $ocConfig;
78
-		$this->notificationManager = $notificationManager;
79
-		$this->userPluginManager = $userPluginManager;
80
-		$this->logger = \OC::$server->get(LoggerInterface::class);
81
-	}
82
-
83
-	/**
84
-	 * checks whether the user is allowed to change his avatar in Nextcloud
85
-	 *
86
-	 * @param string $uid the Nextcloud user name
87
-	 * @return boolean either the user can or cannot
88
-	 * @throws \Exception
89
-	 */
90
-	public function canChangeAvatar($uid) {
91
-		if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
92
-			return $this->userPluginManager->canChangeAvatar($uid);
93
-		}
94
-
95
-		if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
96
-			return true;
97
-		}
98
-
99
-		$user = $this->access->userManager->get($uid);
100
-		if (!$user instanceof User) {
101
-			return false;
102
-		}
103
-		$imageData = $user->getAvatarImage();
104
-		if ($imageData === false) {
105
-			return true;
106
-		}
107
-		return !$user->updateAvatar(true);
108
-	}
109
-
110
-	/**
111
-	 * Return the username for the given login name, if available
112
-	 *
113
-	 * @param string $loginName
114
-	 * @return string|false
115
-	 * @throws \Exception
116
-	 */
117
-	public function loginName2UserName($loginName) {
118
-		$cacheKey = 'loginName2UserName-' . $loginName;
119
-		$username = $this->access->connection->getFromCache($cacheKey);
120
-
121
-		if ($username !== null) {
122
-			return $username;
123
-		}
124
-
125
-		try {
126
-			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
127
-			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
128
-			if ($user === null || $user instanceof OfflineUser) {
129
-				// this path is not really possible, however get() is documented
130
-				// to return User, OfflineUser or null so we are very defensive here.
131
-				$this->access->connection->writeToCache($cacheKey, false);
132
-				return false;
133
-			}
134
-			$username = $user->getUsername();
135
-			$this->access->connection->writeToCache($cacheKey, $username);
136
-			return $username;
137
-		} catch (NotOnLDAP $e) {
138
-			$this->access->connection->writeToCache($cacheKey, false);
139
-			return false;
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * returns the username for the given LDAP DN, if available
145
-	 *
146
-	 * @param string $dn
147
-	 * @return string|false with the username
148
-	 */
149
-	public function dn2UserName($dn) {
150
-		return $this->access->dn2username($dn);
151
-	}
152
-
153
-	/**
154
-	 * returns an LDAP record based on a given login name
155
-	 *
156
-	 * @param string $loginName
157
-	 * @return array
158
-	 * @throws NotOnLDAP
159
-	 */
160
-	public function getLDAPUserByLoginName($loginName) {
161
-		//find out dn of the user name
162
-		$attrs = $this->access->userManager->getAttributes();
163
-		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
164
-		if (count($users) < 1) {
165
-			throw new NotOnLDAP('No user available for the given login name on ' .
166
-				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
167
-		}
168
-		return $users[0];
169
-	}
170
-
171
-	/**
172
-	 * Check if the password is correct without logging in the user
173
-	 *
174
-	 * @param string $uid The username
175
-	 * @param string $password The password
176
-	 * @return false|string
177
-	 */
178
-	public function checkPassword($uid, $password) {
179
-		try {
180
-			$ldapRecord = $this->getLDAPUserByLoginName($uid);
181
-		} catch (NotOnLDAP $e) {
182
-			$this->logger->debug(
183
-				$e->getMessage(),
184
-				['app' => 'user_ldap', 'exception' => $e]
185
-			);
186
-			return false;
187
-		}
188
-		$dn = $ldapRecord['dn'][0];
189
-		$user = $this->access->userManager->get($dn);
190
-
191
-		if (!$user instanceof User) {
192
-			$this->logger->warning(
193
-				'LDAP Login: Could not get user object for DN ' . $dn .
194
-				'. Maybe the LDAP entry has no set display name attribute?',
195
-				['app' => 'user_ldap']
196
-			);
197
-			return false;
198
-		}
199
-		if ($user->getUsername() !== false) {
200
-			//are the credentials OK?
201
-			if (!$this->access->areCredentialsValid($dn, $password)) {
202
-				return false;
203
-			}
204
-
205
-			$this->access->cacheUserExists($user->getUsername());
206
-			$user->processAttributes($ldapRecord);
207
-			$user->markLogin();
208
-
209
-			return $user->getUsername();
210
-		}
211
-
212
-		return false;
213
-	}
214
-
215
-	/**
216
-	 * Set password
217
-	 * @param string $uid The username
218
-	 * @param string $password The new password
219
-	 * @return bool
220
-	 */
221
-	public function setPassword($uid, $password) {
222
-		if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
223
-			return $this->userPluginManager->setPassword($uid, $password);
224
-		}
225
-
226
-		$user = $this->access->userManager->get($uid);
227
-
228
-		if (!$user instanceof User) {
229
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
230
-				'. Maybe the LDAP entry has no set display name attribute?');
231
-		}
232
-		if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
233
-			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
234
-			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
235
-			if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
236
-				//remove last password expiry warning if any
237
-				$notification = $this->notificationManager->createNotification();
238
-				$notification->setApp('user_ldap')
239
-					->setUser($uid)
240
-					->setObject('pwd_exp_warn', $uid)
241
-				;
242
-				$this->notificationManager->markProcessed($notification);
243
-			}
244
-			return true;
245
-		}
246
-
247
-		return false;
248
-	}
249
-
250
-	/**
251
-	 * Get a list of all users
252
-	 *
253
-	 * @param string $search
254
-	 * @param integer $limit
255
-	 * @param integer $offset
256
-	 * @return string[] an array of all uids
257
-	 */
258
-	public function getUsers($search = '', $limit = 10, $offset = 0) {
259
-		$search = $this->access->escapeFilterPart($search, true);
260
-		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
261
-
262
-		//check if users are cached, if so return
263
-		$ldap_users = $this->access->connection->getFromCache($cachekey);
264
-		if (!is_null($ldap_users)) {
265
-			return $ldap_users;
266
-		}
267
-
268
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
269
-		// error. With a limit of 0, we get 0 results. So we pass null.
270
-		if ($limit <= 0) {
271
-			$limit = null;
272
-		}
273
-		$filter = $this->access->combineFilterWithAnd([
274
-			$this->access->connection->ldapUserFilter,
275
-			$this->access->connection->ldapUserDisplayName . '=*',
276
-			$this->access->getFilterPartForUserSearch($search)
277
-		]);
278
-
279
-		$this->logger->debug(
280
-			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
281
-			['app' => 'user_ldap']
282
-		);
283
-		//do the search and translate results to Nextcloud names
284
-		$ldap_users = $this->access->fetchListOfUsers(
285
-			$filter,
286
-			$this->access->userManager->getAttributes(true),
287
-			$limit, $offset);
288
-		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
289
-		$this->logger->debug(
290
-			'getUsers: '.count($ldap_users). ' Users found',
291
-			['app' => 'user_ldap']
292
-		);
293
-
294
-		$this->access->connection->writeToCache($cachekey, $ldap_users);
295
-		return $ldap_users;
296
-	}
297
-
298
-	/**
299
-	 * checks whether a user is still available on LDAP
300
-	 *
301
-	 * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
302
-	 * name or an instance of that user
303
-	 * @throws \Exception
304
-	 * @throws \OC\ServerNotAvailableException
305
-	 */
306
-	public function userExistsOnLDAP($user, bool $ignoreCache = false): bool {
307
-		if (is_string($user)) {
308
-			$user = $this->access->userManager->get($user);
309
-		}
310
-		if (is_null($user)) {
311
-			return false;
312
-		}
313
-		$uid = $user instanceof User ? $user->getUsername() : $user->getOCName();
314
-		$cacheKey = 'userExistsOnLDAP' . $uid;
315
-		if (!$ignoreCache) {
316
-			$userExists = $this->access->connection->getFromCache($cacheKey);
317
-			if (!is_null($userExists)) {
318
-				return (bool)$userExists;
319
-			}
320
-		}
321
-
322
-		$dn = $user->getDN();
323
-		//check if user really still exists by reading its entry
324
-		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
325
-			try {
326
-				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
327
-				if (!$uuid) {
328
-					$this->access->connection->writeToCache($cacheKey, false);
329
-					return false;
330
-				}
331
-				$newDn = $this->access->getUserDnByUuid($uuid);
332
-				//check if renamed user is still valid by reapplying the ldap filter
333
-				if ($newDn === $dn || !is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
334
-					$this->access->connection->writeToCache($cacheKey, false);
335
-					return false;
336
-				}
337
-				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
338
-			} catch (ServerNotAvailableException $e) {
339
-				throw $e;
340
-			} catch (\Exception $e) {
341
-				$this->access->connection->writeToCache($cacheKey, false);
342
-				return false;
343
-			}
344
-		}
345
-
346
-		if ($user instanceof OfflineUser) {
347
-			$user->unmark();
348
-		}
349
-
350
-		$this->access->connection->writeToCache($cacheKey, true);
351
-		return true;
352
-	}
353
-
354
-	/**
355
-	 * check if a user exists
356
-	 * @param string $uid the username
357
-	 * @return boolean
358
-	 * @throws \Exception when connection could not be established
359
-	 */
360
-	public function userExists($uid) {
361
-		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
362
-		if (!is_null($userExists)) {
363
-			return (bool)$userExists;
364
-		}
365
-		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
366
-		$user = $this->access->userManager->get($uid);
367
-
368
-		if (is_null($user)) {
369
-			$this->logger->debug(
370
-				'No DN found for '.$uid.' on '.$this->access->connection->ldapHost,
371
-				['app' => 'user_ldap']
372
-			);
373
-			$this->access->connection->writeToCache('userExists'.$uid, false);
374
-			return false;
375
-		}
376
-
377
-		$this->access->connection->writeToCache('userExists'.$uid, true);
378
-		return true;
379
-	}
380
-
381
-	/**
382
-	 * returns whether a user was deleted in LDAP
383
-	 *
384
-	 * @param string $uid The username of the user to delete
385
-	 * @return bool
386
-	 */
387
-	public function deleteUser($uid) {
388
-		if ($this->userPluginManager->canDeleteUser()) {
389
-			$status = $this->userPluginManager->deleteUser($uid);
390
-			if ($status === false) {
391
-				return false;
392
-			}
393
-		}
394
-
395
-		$marked = (int)$this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
396
-		if ($marked === 0) {
397
-			try {
398
-				$user = $this->access->userManager->get($uid);
399
-				if (($user instanceof User) && !$this->userExistsOnLDAP($uid, true)) {
400
-					$user->markUser();
401
-					$marked = 1;
402
-				}
403
-			} catch (\Exception $e) {
404
-				$this->logger->debug(
405
-					$e->getMessage(),
406
-					['app' => 'user_ldap', 'exception' => $e]
407
-				);
408
-			}
409
-			if ($marked === 0) {
410
-				$this->logger->notice(
411
-					'User '.$uid . ' is not marked as deleted, not cleaning up.',
412
-					['app' => 'user_ldap']
413
-				);
414
-				return false;
415
-			}
416
-		}
417
-		$this->logger->info('Cleaning up after user ' . $uid,
418
-			['app' => 'user_ldap']);
419
-
420
-		$this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
421
-		$this->access->userManager->invalidate($uid);
422
-		$this->access->connection->clearCache();
423
-		return true;
424
-	}
425
-
426
-	/**
427
-	 * get the user's home directory
428
-	 *
429
-	 * @param string $uid the username
430
-	 * @return bool|string
431
-	 * @throws NoUserException
432
-	 * @throws \Exception
433
-	 */
434
-	public function getHome($uid) {
435
-		// user Exists check required as it is not done in user proxy!
436
-		if (!$this->userExists($uid)) {
437
-			return false;
438
-		}
439
-
440
-		if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
441
-			return $this->userPluginManager->getHome($uid);
442
-		}
443
-
444
-		$cacheKey = 'getHome'.$uid;
445
-		$path = $this->access->connection->getFromCache($cacheKey);
446
-		if (!is_null($path)) {
447
-			return $path;
448
-		}
449
-
450
-		// early return path if it is a deleted user
451
-		$user = $this->access->userManager->get($uid);
452
-		if ($user instanceof User || $user instanceof OfflineUser) {
453
-			$path = $user->getHomePath() ?: false;
454
-		} else {
455
-			throw new NoUserException($uid . ' is not a valid user anymore');
456
-		}
457
-
458
-		$this->access->cacheUserHome($uid, $path);
459
-		return $path;
460
-	}
461
-
462
-	/**
463
-	 * get display name of the user
464
-	 * @param string $uid user ID of the user
465
-	 * @return string|false display name
466
-	 */
467
-	public function getDisplayName($uid) {
468
-		if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
469
-			return $this->userPluginManager->getDisplayName($uid);
470
-		}
471
-
472
-		if (!$this->userExists($uid)) {
473
-			return false;
474
-		}
475
-
476
-		$cacheKey = 'getDisplayName'.$uid;
477
-		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
478
-			return $displayName;
479
-		}
480
-
481
-		//Check whether the display name is configured to have a 2nd feature
482
-		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
483
-		$displayName2 = '';
484
-		if ($additionalAttribute !== '') {
485
-			$displayName2 = $this->access->readAttribute(
486
-				$this->access->username2dn($uid),
487
-				$additionalAttribute);
488
-		}
489
-
490
-		$displayName = $this->access->readAttribute(
491
-			$this->access->username2dn($uid),
492
-			$this->access->connection->ldapUserDisplayName);
493
-
494
-		if ($displayName && (count($displayName) > 0)) {
495
-			$displayName = $displayName[0];
496
-
497
-			if (is_array($displayName2)) {
498
-				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
499
-			}
500
-
501
-			$user = $this->access->userManager->get($uid);
502
-			if ($user instanceof User) {
503
-				$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
504
-				$this->access->connection->writeToCache($cacheKey, $displayName);
505
-			}
506
-			if ($user instanceof OfflineUser) {
507
-				/** @var OfflineUser $user*/
508
-				$displayName = $user->getDisplayName();
509
-			}
510
-			return $displayName;
511
-		}
512
-
513
-		return null;
514
-	}
515
-
516
-	/**
517
-	 * set display name of the user
518
-	 * @param string $uid user ID of the user
519
-	 * @param string $displayName new display name of the user
520
-	 * @return string|false display name
521
-	 */
522
-	public function setDisplayName($uid, $displayName) {
523
-		if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
524
-			$this->userPluginManager->setDisplayName($uid, $displayName);
525
-			$this->access->cacheUserDisplayName($uid, $displayName);
526
-			return $displayName;
527
-		}
528
-		return false;
529
-	}
530
-
531
-	/**
532
-	 * Get a list of all display names
533
-	 *
534
-	 * @param string $search
535
-	 * @param int|null $limit
536
-	 * @param int|null $offset
537
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
538
-	 */
539
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
540
-		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
541
-		if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
542
-			return $displayNames;
543
-		}
544
-
545
-		$displayNames = [];
546
-		$users = $this->getUsers($search, $limit, $offset);
547
-		foreach ($users as $user) {
548
-			$displayNames[$user] = $this->getDisplayName($user);
549
-		}
550
-		$this->access->connection->writeToCache($cacheKey, $displayNames);
551
-		return $displayNames;
552
-	}
553
-
554
-	/**
555
-	 * Check if backend implements actions
556
-	 * @param int $actions bitwise-or'ed actions
557
-	 * @return boolean
558
-	 *
559
-	 * Returns the supported actions as int to be
560
-	 * compared with \OC\User\Backend::CREATE_USER etc.
561
-	 */
562
-	public function implementsActions($actions) {
563
-		return (bool)((Backend::CHECK_PASSWORD
564
-			| Backend::GET_HOME
565
-			| Backend::GET_DISPLAYNAME
566
-			| (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0)
567
-			| Backend::COUNT_USERS
568
-			| (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
569
-			| $this->userPluginManager->getImplementedActions())
570
-			& $actions);
571
-	}
572
-
573
-	/**
574
-	 * @return bool
575
-	 */
576
-	public function hasUserListings() {
577
-		return true;
578
-	}
579
-
580
-	/**
581
-	 * counts the users in LDAP
582
-	 *
583
-	 * @return int|false
584
-	 */
585
-	public function countUsers() {
586
-		if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
587
-			return $this->userPluginManager->countUsers();
588
-		}
589
-
590
-		$filter = $this->access->getFilterForUserCount();
591
-		$cacheKey = 'countUsers-'.$filter;
592
-		if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
593
-			return $entries;
594
-		}
595
-		$entries = $this->access->countUsers($filter);
596
-		$this->access->connection->writeToCache($cacheKey, $entries);
597
-		return $entries;
598
-	}
599
-
600
-	public function countMappedUsers(): int {
601
-		return $this->access->getUserMapper()->count();
602
-	}
603
-
604
-	/**
605
-	 * Backend name to be shown in user management
606
-	 * @return string the name of the backend to be shown
607
-	 */
608
-	public function getBackendName() {
609
-		return 'LDAP';
610
-	}
611
-
612
-	/**
613
-	 * Return access for LDAP interaction.
614
-	 * @param string $uid
615
-	 * @return Access instance of Access for LDAP interaction
616
-	 */
617
-	public function getLDAPAccess($uid) {
618
-		return $this->access;
619
-	}
620
-
621
-	/**
622
-	 * Return LDAP connection resource from a cloned connection.
623
-	 * The cloned connection needs to be closed manually.
624
-	 * of the current access.
625
-	 * @param string $uid
626
-	 * @return resource|\LDAP\Connection The LDAP connection
627
-	 */
628
-	public function getNewLDAPConnection($uid) {
629
-		$connection = clone $this->access->getConnection();
630
-		return $connection->getConnectionResource();
631
-	}
632
-
633
-	/**
634
-	 * create new user
635
-	 * @param string $username username of the new user
636
-	 * @param string $password password of the new user
637
-	 * @throws \UnexpectedValueException
638
-	 * @return bool
639
-	 */
640
-	public function createUser($username, $password) {
641
-		if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
642
-			if ($dn = $this->userPluginManager->createUser($username, $password)) {
643
-				if (is_string($dn)) {
644
-					// the NC user creation work flow requires a know user id up front
645
-					$uuid = $this->access->getUUID($dn, true);
646
-					if (is_string($uuid)) {
647
-						$this->access->mapAndAnnounceIfApplicable(
648
-							$this->access->getUserMapper(),
649
-							$dn,
650
-							$username,
651
-							$uuid,
652
-							true
653
-						);
654
-						$this->access->cacheUserExists($username);
655
-					} else {
656
-						$this->logger->warning(
657
-							'Failed to map created LDAP user with userid {userid}, because UUID could not be determined',
658
-							[
659
-								'app' => 'user_ldap',
660
-								'userid' => $username,
661
-							]
662
-						);
663
-					}
664
-				} else {
665
-					throw new \UnexpectedValueException("LDAP Plugin: Method createUser changed to return the user DN instead of boolean.");
666
-				}
667
-			}
668
-			return (bool) $dn;
669
-		}
670
-		return false;
671
-	}
57
+    /** @var \OCP\IConfig */
58
+    protected $ocConfig;
59
+
60
+    /** @var INotificationManager */
61
+    protected $notificationManager;
62
+
63
+    /** @var UserPluginManager */
64
+    protected $userPluginManager;
65
+
66
+    /** @var LoggerInterface */
67
+    protected $logger;
68
+
69
+    /**
70
+     * @param Access $access
71
+     * @param \OCP\IConfig $ocConfig
72
+     * @param \OCP\Notification\IManager $notificationManager
73
+     * @param IUserSession $userSession
74
+     */
75
+    public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession, UserPluginManager $userPluginManager) {
76
+        parent::__construct($access);
77
+        $this->ocConfig = $ocConfig;
78
+        $this->notificationManager = $notificationManager;
79
+        $this->userPluginManager = $userPluginManager;
80
+        $this->logger = \OC::$server->get(LoggerInterface::class);
81
+    }
82
+
83
+    /**
84
+     * checks whether the user is allowed to change his avatar in Nextcloud
85
+     *
86
+     * @param string $uid the Nextcloud user name
87
+     * @return boolean either the user can or cannot
88
+     * @throws \Exception
89
+     */
90
+    public function canChangeAvatar($uid) {
91
+        if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
92
+            return $this->userPluginManager->canChangeAvatar($uid);
93
+        }
94
+
95
+        if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
96
+            return true;
97
+        }
98
+
99
+        $user = $this->access->userManager->get($uid);
100
+        if (!$user instanceof User) {
101
+            return false;
102
+        }
103
+        $imageData = $user->getAvatarImage();
104
+        if ($imageData === false) {
105
+            return true;
106
+        }
107
+        return !$user->updateAvatar(true);
108
+    }
109
+
110
+    /**
111
+     * Return the username for the given login name, if available
112
+     *
113
+     * @param string $loginName
114
+     * @return string|false
115
+     * @throws \Exception
116
+     */
117
+    public function loginName2UserName($loginName) {
118
+        $cacheKey = 'loginName2UserName-' . $loginName;
119
+        $username = $this->access->connection->getFromCache($cacheKey);
120
+
121
+        if ($username !== null) {
122
+            return $username;
123
+        }
124
+
125
+        try {
126
+            $ldapRecord = $this->getLDAPUserByLoginName($loginName);
127
+            $user = $this->access->userManager->get($ldapRecord['dn'][0]);
128
+            if ($user === null || $user instanceof OfflineUser) {
129
+                // this path is not really possible, however get() is documented
130
+                // to return User, OfflineUser or null so we are very defensive here.
131
+                $this->access->connection->writeToCache($cacheKey, false);
132
+                return false;
133
+            }
134
+            $username = $user->getUsername();
135
+            $this->access->connection->writeToCache($cacheKey, $username);
136
+            return $username;
137
+        } catch (NotOnLDAP $e) {
138
+            $this->access->connection->writeToCache($cacheKey, false);
139
+            return false;
140
+        }
141
+    }
142
+
143
+    /**
144
+     * returns the username for the given LDAP DN, if available
145
+     *
146
+     * @param string $dn
147
+     * @return string|false with the username
148
+     */
149
+    public function dn2UserName($dn) {
150
+        return $this->access->dn2username($dn);
151
+    }
152
+
153
+    /**
154
+     * returns an LDAP record based on a given login name
155
+     *
156
+     * @param string $loginName
157
+     * @return array
158
+     * @throws NotOnLDAP
159
+     */
160
+    public function getLDAPUserByLoginName($loginName) {
161
+        //find out dn of the user name
162
+        $attrs = $this->access->userManager->getAttributes();
163
+        $users = $this->access->fetchUsersByLoginName($loginName, $attrs);
164
+        if (count($users) < 1) {
165
+            throw new NotOnLDAP('No user available for the given login name on ' .
166
+                $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
167
+        }
168
+        return $users[0];
169
+    }
170
+
171
+    /**
172
+     * Check if the password is correct without logging in the user
173
+     *
174
+     * @param string $uid The username
175
+     * @param string $password The password
176
+     * @return false|string
177
+     */
178
+    public function checkPassword($uid, $password) {
179
+        try {
180
+            $ldapRecord = $this->getLDAPUserByLoginName($uid);
181
+        } catch (NotOnLDAP $e) {
182
+            $this->logger->debug(
183
+                $e->getMessage(),
184
+                ['app' => 'user_ldap', 'exception' => $e]
185
+            );
186
+            return false;
187
+        }
188
+        $dn = $ldapRecord['dn'][0];
189
+        $user = $this->access->userManager->get($dn);
190
+
191
+        if (!$user instanceof User) {
192
+            $this->logger->warning(
193
+                'LDAP Login: Could not get user object for DN ' . $dn .
194
+                '. Maybe the LDAP entry has no set display name attribute?',
195
+                ['app' => 'user_ldap']
196
+            );
197
+            return false;
198
+        }
199
+        if ($user->getUsername() !== false) {
200
+            //are the credentials OK?
201
+            if (!$this->access->areCredentialsValid($dn, $password)) {
202
+                return false;
203
+            }
204
+
205
+            $this->access->cacheUserExists($user->getUsername());
206
+            $user->processAttributes($ldapRecord);
207
+            $user->markLogin();
208
+
209
+            return $user->getUsername();
210
+        }
211
+
212
+        return false;
213
+    }
214
+
215
+    /**
216
+     * Set password
217
+     * @param string $uid The username
218
+     * @param string $password The new password
219
+     * @return bool
220
+     */
221
+    public function setPassword($uid, $password) {
222
+        if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
223
+            return $this->userPluginManager->setPassword($uid, $password);
224
+        }
225
+
226
+        $user = $this->access->userManager->get($uid);
227
+
228
+        if (!$user instanceof User) {
229
+            throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
230
+                '. Maybe the LDAP entry has no set display name attribute?');
231
+        }
232
+        if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
233
+            $ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
234
+            $turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
235
+            if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
236
+                //remove last password expiry warning if any
237
+                $notification = $this->notificationManager->createNotification();
238
+                $notification->setApp('user_ldap')
239
+                    ->setUser($uid)
240
+                    ->setObject('pwd_exp_warn', $uid)
241
+                ;
242
+                $this->notificationManager->markProcessed($notification);
243
+            }
244
+            return true;
245
+        }
246
+
247
+        return false;
248
+    }
249
+
250
+    /**
251
+     * Get a list of all users
252
+     *
253
+     * @param string $search
254
+     * @param integer $limit
255
+     * @param integer $offset
256
+     * @return string[] an array of all uids
257
+     */
258
+    public function getUsers($search = '', $limit = 10, $offset = 0) {
259
+        $search = $this->access->escapeFilterPart($search, true);
260
+        $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
261
+
262
+        //check if users are cached, if so return
263
+        $ldap_users = $this->access->connection->getFromCache($cachekey);
264
+        if (!is_null($ldap_users)) {
265
+            return $ldap_users;
266
+        }
267
+
268
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
269
+        // error. With a limit of 0, we get 0 results. So we pass null.
270
+        if ($limit <= 0) {
271
+            $limit = null;
272
+        }
273
+        $filter = $this->access->combineFilterWithAnd([
274
+            $this->access->connection->ldapUserFilter,
275
+            $this->access->connection->ldapUserDisplayName . '=*',
276
+            $this->access->getFilterPartForUserSearch($search)
277
+        ]);
278
+
279
+        $this->logger->debug(
280
+            'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
281
+            ['app' => 'user_ldap']
282
+        );
283
+        //do the search and translate results to Nextcloud names
284
+        $ldap_users = $this->access->fetchListOfUsers(
285
+            $filter,
286
+            $this->access->userManager->getAttributes(true),
287
+            $limit, $offset);
288
+        $ldap_users = $this->access->nextcloudUserNames($ldap_users);
289
+        $this->logger->debug(
290
+            'getUsers: '.count($ldap_users). ' Users found',
291
+            ['app' => 'user_ldap']
292
+        );
293
+
294
+        $this->access->connection->writeToCache($cachekey, $ldap_users);
295
+        return $ldap_users;
296
+    }
297
+
298
+    /**
299
+     * checks whether a user is still available on LDAP
300
+     *
301
+     * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
302
+     * name or an instance of that user
303
+     * @throws \Exception
304
+     * @throws \OC\ServerNotAvailableException
305
+     */
306
+    public function userExistsOnLDAP($user, bool $ignoreCache = false): bool {
307
+        if (is_string($user)) {
308
+            $user = $this->access->userManager->get($user);
309
+        }
310
+        if (is_null($user)) {
311
+            return false;
312
+        }
313
+        $uid = $user instanceof User ? $user->getUsername() : $user->getOCName();
314
+        $cacheKey = 'userExistsOnLDAP' . $uid;
315
+        if (!$ignoreCache) {
316
+            $userExists = $this->access->connection->getFromCache($cacheKey);
317
+            if (!is_null($userExists)) {
318
+                return (bool)$userExists;
319
+            }
320
+        }
321
+
322
+        $dn = $user->getDN();
323
+        //check if user really still exists by reading its entry
324
+        if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
325
+            try {
326
+                $uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
327
+                if (!$uuid) {
328
+                    $this->access->connection->writeToCache($cacheKey, false);
329
+                    return false;
330
+                }
331
+                $newDn = $this->access->getUserDnByUuid($uuid);
332
+                //check if renamed user is still valid by reapplying the ldap filter
333
+                if ($newDn === $dn || !is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
334
+                    $this->access->connection->writeToCache($cacheKey, false);
335
+                    return false;
336
+                }
337
+                $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
338
+            } catch (ServerNotAvailableException $e) {
339
+                throw $e;
340
+            } catch (\Exception $e) {
341
+                $this->access->connection->writeToCache($cacheKey, false);
342
+                return false;
343
+            }
344
+        }
345
+
346
+        if ($user instanceof OfflineUser) {
347
+            $user->unmark();
348
+        }
349
+
350
+        $this->access->connection->writeToCache($cacheKey, true);
351
+        return true;
352
+    }
353
+
354
+    /**
355
+     * check if a user exists
356
+     * @param string $uid the username
357
+     * @return boolean
358
+     * @throws \Exception when connection could not be established
359
+     */
360
+    public function userExists($uid) {
361
+        $userExists = $this->access->connection->getFromCache('userExists'.$uid);
362
+        if (!is_null($userExists)) {
363
+            return (bool)$userExists;
364
+        }
365
+        //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
366
+        $user = $this->access->userManager->get($uid);
367
+
368
+        if (is_null($user)) {
369
+            $this->logger->debug(
370
+                'No DN found for '.$uid.' on '.$this->access->connection->ldapHost,
371
+                ['app' => 'user_ldap']
372
+            );
373
+            $this->access->connection->writeToCache('userExists'.$uid, false);
374
+            return false;
375
+        }
376
+
377
+        $this->access->connection->writeToCache('userExists'.$uid, true);
378
+        return true;
379
+    }
380
+
381
+    /**
382
+     * returns whether a user was deleted in LDAP
383
+     *
384
+     * @param string $uid The username of the user to delete
385
+     * @return bool
386
+     */
387
+    public function deleteUser($uid) {
388
+        if ($this->userPluginManager->canDeleteUser()) {
389
+            $status = $this->userPluginManager->deleteUser($uid);
390
+            if ($status === false) {
391
+                return false;
392
+            }
393
+        }
394
+
395
+        $marked = (int)$this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
396
+        if ($marked === 0) {
397
+            try {
398
+                $user = $this->access->userManager->get($uid);
399
+                if (($user instanceof User) && !$this->userExistsOnLDAP($uid, true)) {
400
+                    $user->markUser();
401
+                    $marked = 1;
402
+                }
403
+            } catch (\Exception $e) {
404
+                $this->logger->debug(
405
+                    $e->getMessage(),
406
+                    ['app' => 'user_ldap', 'exception' => $e]
407
+                );
408
+            }
409
+            if ($marked === 0) {
410
+                $this->logger->notice(
411
+                    'User '.$uid . ' is not marked as deleted, not cleaning up.',
412
+                    ['app' => 'user_ldap']
413
+                );
414
+                return false;
415
+            }
416
+        }
417
+        $this->logger->info('Cleaning up after user ' . $uid,
418
+            ['app' => 'user_ldap']);
419
+
420
+        $this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
421
+        $this->access->userManager->invalidate($uid);
422
+        $this->access->connection->clearCache();
423
+        return true;
424
+    }
425
+
426
+    /**
427
+     * get the user's home directory
428
+     *
429
+     * @param string $uid the username
430
+     * @return bool|string
431
+     * @throws NoUserException
432
+     * @throws \Exception
433
+     */
434
+    public function getHome($uid) {
435
+        // user Exists check required as it is not done in user proxy!
436
+        if (!$this->userExists($uid)) {
437
+            return false;
438
+        }
439
+
440
+        if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
441
+            return $this->userPluginManager->getHome($uid);
442
+        }
443
+
444
+        $cacheKey = 'getHome'.$uid;
445
+        $path = $this->access->connection->getFromCache($cacheKey);
446
+        if (!is_null($path)) {
447
+            return $path;
448
+        }
449
+
450
+        // early return path if it is a deleted user
451
+        $user = $this->access->userManager->get($uid);
452
+        if ($user instanceof User || $user instanceof OfflineUser) {
453
+            $path = $user->getHomePath() ?: false;
454
+        } else {
455
+            throw new NoUserException($uid . ' is not a valid user anymore');
456
+        }
457
+
458
+        $this->access->cacheUserHome($uid, $path);
459
+        return $path;
460
+    }
461
+
462
+    /**
463
+     * get display name of the user
464
+     * @param string $uid user ID of the user
465
+     * @return string|false display name
466
+     */
467
+    public function getDisplayName($uid) {
468
+        if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
469
+            return $this->userPluginManager->getDisplayName($uid);
470
+        }
471
+
472
+        if (!$this->userExists($uid)) {
473
+            return false;
474
+        }
475
+
476
+        $cacheKey = 'getDisplayName'.$uid;
477
+        if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
478
+            return $displayName;
479
+        }
480
+
481
+        //Check whether the display name is configured to have a 2nd feature
482
+        $additionalAttribute = $this->access->connection->ldapUserDisplayName2;
483
+        $displayName2 = '';
484
+        if ($additionalAttribute !== '') {
485
+            $displayName2 = $this->access->readAttribute(
486
+                $this->access->username2dn($uid),
487
+                $additionalAttribute);
488
+        }
489
+
490
+        $displayName = $this->access->readAttribute(
491
+            $this->access->username2dn($uid),
492
+            $this->access->connection->ldapUserDisplayName);
493
+
494
+        if ($displayName && (count($displayName) > 0)) {
495
+            $displayName = $displayName[0];
496
+
497
+            if (is_array($displayName2)) {
498
+                $displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
499
+            }
500
+
501
+            $user = $this->access->userManager->get($uid);
502
+            if ($user instanceof User) {
503
+                $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
504
+                $this->access->connection->writeToCache($cacheKey, $displayName);
505
+            }
506
+            if ($user instanceof OfflineUser) {
507
+                /** @var OfflineUser $user*/
508
+                $displayName = $user->getDisplayName();
509
+            }
510
+            return $displayName;
511
+        }
512
+
513
+        return null;
514
+    }
515
+
516
+    /**
517
+     * set display name of the user
518
+     * @param string $uid user ID of the user
519
+     * @param string $displayName new display name of the user
520
+     * @return string|false display name
521
+     */
522
+    public function setDisplayName($uid, $displayName) {
523
+        if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
524
+            $this->userPluginManager->setDisplayName($uid, $displayName);
525
+            $this->access->cacheUserDisplayName($uid, $displayName);
526
+            return $displayName;
527
+        }
528
+        return false;
529
+    }
530
+
531
+    /**
532
+     * Get a list of all display names
533
+     *
534
+     * @param string $search
535
+     * @param int|null $limit
536
+     * @param int|null $offset
537
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
538
+     */
539
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
540
+        $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
541
+        if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
542
+            return $displayNames;
543
+        }
544
+
545
+        $displayNames = [];
546
+        $users = $this->getUsers($search, $limit, $offset);
547
+        foreach ($users as $user) {
548
+            $displayNames[$user] = $this->getDisplayName($user);
549
+        }
550
+        $this->access->connection->writeToCache($cacheKey, $displayNames);
551
+        return $displayNames;
552
+    }
553
+
554
+    /**
555
+     * Check if backend implements actions
556
+     * @param int $actions bitwise-or'ed actions
557
+     * @return boolean
558
+     *
559
+     * Returns the supported actions as int to be
560
+     * compared with \OC\User\Backend::CREATE_USER etc.
561
+     */
562
+    public function implementsActions($actions) {
563
+        return (bool)((Backend::CHECK_PASSWORD
564
+            | Backend::GET_HOME
565
+            | Backend::GET_DISPLAYNAME
566
+            | (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0)
567
+            | Backend::COUNT_USERS
568
+            | (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
569
+            | $this->userPluginManager->getImplementedActions())
570
+            & $actions);
571
+    }
572
+
573
+    /**
574
+     * @return bool
575
+     */
576
+    public function hasUserListings() {
577
+        return true;
578
+    }
579
+
580
+    /**
581
+     * counts the users in LDAP
582
+     *
583
+     * @return int|false
584
+     */
585
+    public function countUsers() {
586
+        if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
587
+            return $this->userPluginManager->countUsers();
588
+        }
589
+
590
+        $filter = $this->access->getFilterForUserCount();
591
+        $cacheKey = 'countUsers-'.$filter;
592
+        if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
593
+            return $entries;
594
+        }
595
+        $entries = $this->access->countUsers($filter);
596
+        $this->access->connection->writeToCache($cacheKey, $entries);
597
+        return $entries;
598
+    }
599
+
600
+    public function countMappedUsers(): int {
601
+        return $this->access->getUserMapper()->count();
602
+    }
603
+
604
+    /**
605
+     * Backend name to be shown in user management
606
+     * @return string the name of the backend to be shown
607
+     */
608
+    public function getBackendName() {
609
+        return 'LDAP';
610
+    }
611
+
612
+    /**
613
+     * Return access for LDAP interaction.
614
+     * @param string $uid
615
+     * @return Access instance of Access for LDAP interaction
616
+     */
617
+    public function getLDAPAccess($uid) {
618
+        return $this->access;
619
+    }
620
+
621
+    /**
622
+     * Return LDAP connection resource from a cloned connection.
623
+     * The cloned connection needs to be closed manually.
624
+     * of the current access.
625
+     * @param string $uid
626
+     * @return resource|\LDAP\Connection The LDAP connection
627
+     */
628
+    public function getNewLDAPConnection($uid) {
629
+        $connection = clone $this->access->getConnection();
630
+        return $connection->getConnectionResource();
631
+    }
632
+
633
+    /**
634
+     * create new user
635
+     * @param string $username username of the new user
636
+     * @param string $password password of the new user
637
+     * @throws \UnexpectedValueException
638
+     * @return bool
639
+     */
640
+    public function createUser($username, $password) {
641
+        if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
642
+            if ($dn = $this->userPluginManager->createUser($username, $password)) {
643
+                if (is_string($dn)) {
644
+                    // the NC user creation work flow requires a know user id up front
645
+                    $uuid = $this->access->getUUID($dn, true);
646
+                    if (is_string($uuid)) {
647
+                        $this->access->mapAndAnnounceIfApplicable(
648
+                            $this->access->getUserMapper(),
649
+                            $dn,
650
+                            $username,
651
+                            $uuid,
652
+                            true
653
+                        );
654
+                        $this->access->cacheUserExists($username);
655
+                    } else {
656
+                        $this->logger->warning(
657
+                            'Failed to map created LDAP user with userid {userid}, because UUID could not be determined',
658
+                            [
659
+                                'app' => 'user_ldap',
660
+                                'userid' => $username,
661
+                            ]
662
+                        );
663
+                    }
664
+                } else {
665
+                    throw new \UnexpectedValueException("LDAP Plugin: Method createUser changed to return the user DN instead of boolean.");
666
+                }
667
+            }
668
+            return (bool) $dn;
669
+        }
670
+        return false;
671
+    }
672 672
 }
Please login to merge, or discard this patch.