Completed
Pull Request — master (#10034)
by Blizzz
21:23
created
apps/user_ldap/lib/User/User.php 2 patches
Indentation   +640 added lines, -640 removed lines patch added patch discarded remove patch
@@ -47,650 +47,650 @@
 block discarded – undo
47 47
  * represents an LDAP user, gets and holds user-specific information from LDAP
48 48
  */
49 49
 class User {
50
-	/**
51
-	 * @var IUserTools
52
-	 */
53
-	protected $access;
54
-	/**
55
-	 * @var Connection
56
-	 */
57
-	protected $connection;
58
-	/**
59
-	 * @var IConfig
60
-	 */
61
-	protected $config;
62
-	/**
63
-	 * @var FilesystemHelper
64
-	 */
65
-	protected $fs;
66
-	/**
67
-	 * @var Image
68
-	 */
69
-	protected $image;
70
-	/**
71
-	 * @var LogWrapper
72
-	 */
73
-	protected $log;
74
-	/**
75
-	 * @var IAvatarManager
76
-	 */
77
-	protected $avatarManager;
78
-	/**
79
-	 * @var IUserManager
80
-	 */
81
-	protected $userManager;
82
-	/**
83
-	 * @var INotificationManager
84
-	 */
85
-	protected $notificationManager;
86
-	/**
87
-	 * @var string
88
-	 */
89
-	protected $dn;
90
-	/**
91
-	 * @var string
92
-	 */
93
-	protected $uid;
94
-	/**
95
-	 * @var string[]
96
-	 */
97
-	protected $refreshedFeatures = array();
98
-	/**
99
-	 * @var string
100
-	 */
101
-	protected $avatarImage;
102
-
103
-	/**
104
-	 * DB config keys for user preferences
105
-	 */
106
-	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
107
-	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
108
-
109
-	/**
110
-	 * @brief constructor, make sure the subclasses call this one!
111
-	 * @param string $username the internal username
112
-	 * @param string $dn the LDAP DN
113
-	 * @param IUserTools $access an instance that implements IUserTools for
114
-	 * LDAP interaction
115
-	 * @param IConfig $config
116
-	 * @param FilesystemHelper $fs
117
-	 * @param Image $image any empty instance
118
-	 * @param LogWrapper $log
119
-	 * @param IAvatarManager $avatarManager
120
-	 * @param IUserManager $userManager
121
-	 * @param INotificationManager $notificationManager
122
-	 */
123
-	public function __construct($username, $dn, IUserTools $access,
124
-		IConfig $config, FilesystemHelper $fs, Image $image,
125
-		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
126
-		INotificationManager $notificationManager) {
50
+    /**
51
+     * @var IUserTools
52
+     */
53
+    protected $access;
54
+    /**
55
+     * @var Connection
56
+     */
57
+    protected $connection;
58
+    /**
59
+     * @var IConfig
60
+     */
61
+    protected $config;
62
+    /**
63
+     * @var FilesystemHelper
64
+     */
65
+    protected $fs;
66
+    /**
67
+     * @var Image
68
+     */
69
+    protected $image;
70
+    /**
71
+     * @var LogWrapper
72
+     */
73
+    protected $log;
74
+    /**
75
+     * @var IAvatarManager
76
+     */
77
+    protected $avatarManager;
78
+    /**
79
+     * @var IUserManager
80
+     */
81
+    protected $userManager;
82
+    /**
83
+     * @var INotificationManager
84
+     */
85
+    protected $notificationManager;
86
+    /**
87
+     * @var string
88
+     */
89
+    protected $dn;
90
+    /**
91
+     * @var string
92
+     */
93
+    protected $uid;
94
+    /**
95
+     * @var string[]
96
+     */
97
+    protected $refreshedFeatures = array();
98
+    /**
99
+     * @var string
100
+     */
101
+    protected $avatarImage;
102
+
103
+    /**
104
+     * DB config keys for user preferences
105
+     */
106
+    const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
107
+    const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
108
+
109
+    /**
110
+     * @brief constructor, make sure the subclasses call this one!
111
+     * @param string $username the internal username
112
+     * @param string $dn the LDAP DN
113
+     * @param IUserTools $access an instance that implements IUserTools for
114
+     * LDAP interaction
115
+     * @param IConfig $config
116
+     * @param FilesystemHelper $fs
117
+     * @param Image $image any empty instance
118
+     * @param LogWrapper $log
119
+     * @param IAvatarManager $avatarManager
120
+     * @param IUserManager $userManager
121
+     * @param INotificationManager $notificationManager
122
+     */
123
+    public function __construct($username, $dn, IUserTools $access,
124
+        IConfig $config, FilesystemHelper $fs, Image $image,
125
+        LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
126
+        INotificationManager $notificationManager) {
127 127
 	
128
-		if ($username === null) {
129
-			$log->log("uid for '$dn' must not be null!", ILogger::ERROR);
130
-			throw new \InvalidArgumentException('uid must not be null!');
131
-		} else if ($username === '') {
132
-			$log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
133
-			throw new \InvalidArgumentException('uid must not be an empty string!');
134
-		}
135
-
136
-		$this->access              = $access;
137
-		$this->connection          = $access->getConnection();
138
-		$this->config              = $config;
139
-		$this->fs                  = $fs;
140
-		$this->dn                  = $dn;
141
-		$this->uid                 = $username;
142
-		$this->image               = $image;
143
-		$this->log                 = $log;
144
-		$this->avatarManager       = $avatarManager;
145
-		$this->userManager         = $userManager;
146
-		$this->notificationManager = $notificationManager;
147
-
148
-		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
149
-	}
150
-
151
-	/**
152
-	 * @brief updates properties like email, quota or avatar provided by LDAP
153
-	 * @return null
154
-	 */
155
-	public function update() {
156
-		if(is_null($this->dn)) {
157
-			return null;
158
-		}
159
-
160
-		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161
-				self::USER_PREFKEY_FIRSTLOGIN, 0);
162
-
163
-		if($this->needsRefresh()) {
164
-			$this->updateEmail();
165
-			$this->updateQuota();
166
-			if($hasLoggedIn !== 0) {
167
-				//we do not need to try it, when the user has not been logged in
168
-				//before, because the file system will not be ready.
169
-				$this->updateAvatar();
170
-				//in order to get an avatar as soon as possible, mark the user
171
-				//as refreshed only when updating the avatar did happen
172
-				$this->markRefreshTime();
173
-			}
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
179
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
180
-	 */
181
-	public function processAttributes($ldapEntry) {
182
-		$this->markRefreshTime();
183
-		//Quota
184
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
185
-		if(isset($ldapEntry[$attr])) {
186
-			$this->updateQuota($ldapEntry[$attr][0]);
187
-		} else {
188
-			if ($this->connection->ldapQuotaDefault !== '') {
189
-				$this->updateQuota();
190
-			}
191
-		}
192
-		unset($attr);
193
-
194
-		//displayName
195
-		$displayName = $displayName2 = '';
196
-		$attr = strtolower($this->connection->ldapUserDisplayName);
197
-		if(isset($ldapEntry[$attr])) {
198
-			$displayName = (string)$ldapEntry[$attr][0];
199
-		}
200
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
201
-		if(isset($ldapEntry[$attr])) {
202
-			$displayName2 = (string)$ldapEntry[$attr][0];
203
-		}
204
-		if ($displayName !== '') {
205
-			$this->composeAndStoreDisplayName($displayName);
206
-			$this->access->cacheUserDisplayName(
207
-				$this->getUsername(),
208
-				$displayName,
209
-				$displayName2
210
-			);
211
-		}
212
-		unset($attr);
213
-
214
-		//Email
215
-		//email must be stored after displayname, because it would cause a user
216
-		//change event that will trigger fetching the display name again
217
-		$attr = strtolower($this->connection->ldapEmailAttribute);
218
-		if(isset($ldapEntry[$attr])) {
219
-			$this->updateEmail($ldapEntry[$attr][0]);
220
-		}
221
-		unset($attr);
222
-
223
-		// LDAP Username, needed for s2s sharing
224
-		if(isset($ldapEntry['uid'])) {
225
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
226
-		} else if(isset($ldapEntry['samaccountname'])) {
227
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228
-		}
229
-
230
-		//homePath
231
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
-			if(isset($ldapEntry[$attr])) {
234
-				$this->access->cacheUserHome(
235
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236
-			}
237
-		}
238
-
239
-		//memberOf groups
240
-		$cacheKey = 'getMemberOf'.$this->getUsername();
241
-		$groups = false;
242
-		if(isset($ldapEntry['memberof'])) {
243
-			$groups = $ldapEntry['memberof'];
244
-		}
245
-		$this->connection->writeToCache($cacheKey, $groups);
246
-
247
-		//Avatar
248
-		$attrs = array('jpegphoto', 'thumbnailphoto');
249
-		foreach ($attrs as $attr)  {
250
-			if(isset($ldapEntry[$attr])) {
251
-				$this->avatarImage = $ldapEntry[$attr][0];
252
-				// the call to the method that saves the avatar in the file
253
-				// system must be postponed after the login. It is to ensure
254
-				// external mounts are mounted properly (e.g. with login
255
-				// credentials from the session).
256
-				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
257
-				break;
258
-			}
259
-		}
260
-	}
261
-
262
-	/**
263
-	 * @brief returns the LDAP DN of the user
264
-	 * @return string
265
-	 */
266
-	public function getDN() {
267
-		return $this->dn;
268
-	}
269
-
270
-	/**
271
-	 * @brief returns the Nextcloud internal username of the user
272
-	 * @return string
273
-	 */
274
-	public function getUsername() {
275
-		return $this->uid;
276
-	}
277
-
278
-	/**
279
-	 * returns the home directory of the user if specified by LDAP settings
280
-	 * @param string $valueFromLDAP
281
-	 * @return bool|string
282
-	 * @throws \Exception
283
-	 */
284
-	public function getHomePath($valueFromLDAP = null) {
285
-		$path = (string)$valueFromLDAP;
286
-		$attr = null;
287
-
288
-		if (is_null($valueFromLDAP)
289
-		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
290
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
291
-		{
292
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
293
-			$homedir = $this->access->readAttribute(
294
-				$this->access->username2dn($this->getUsername()), $attr);
295
-			if ($homedir && isset($homedir[0])) {
296
-				$path = $homedir[0];
297
-			}
298
-		}
299
-
300
-		if ($path !== '') {
301
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
302
-			//check for / at the beginning or pattern c:\ resp. c:/
303
-			if(   '/' !== $path[0]
304
-			   && !(3 < strlen($path) && ctype_alpha($path[0])
305
-			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306
-			) {
307
-				$path = $this->config->getSystemValue('datadirectory',
308
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
309
-			}
310
-			//we need it to store it in the DB as well in case a user gets
311
-			//deleted so we can clean up afterwards
312
-			$this->config->setUserValue(
313
-				$this->getUsername(), 'user_ldap', 'homePath', $path
314
-			);
315
-			return $path;
316
-		}
317
-
318
-		if(    !is_null($attr)
319
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320
-		) {
321
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
323
-		}
324
-
325
-		//false will apply default behaviour as defined and done by OC_User
326
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
327
-		return false;
328
-	}
329
-
330
-	public function getMemberOfGroups() {
331
-		$cacheKey = 'getMemberOf'.$this->getUsername();
332
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
333
-		if(!is_null($memberOfGroups)) {
334
-			return $memberOfGroups;
335
-		}
336
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
337
-		$this->connection->writeToCache($cacheKey, $groupDNs);
338
-		return $groupDNs;
339
-	}
340
-
341
-	/**
342
-	 * @brief reads the image from LDAP that shall be used as Avatar
343
-	 * @return string data (provided by LDAP) | false
344
-	 */
345
-	public function getAvatarImage() {
346
-		if(!is_null($this->avatarImage)) {
347
-			return $this->avatarImage;
348
-		}
349
-
350
-		$this->avatarImage = false;
351
-		$attributes = array('jpegPhoto', 'thumbnailPhoto');
352
-		foreach($attributes as $attribute) {
353
-			$result = $this->access->readAttribute($this->dn, $attribute);
354
-			if($result !== false && is_array($result) && isset($result[0])) {
355
-				$this->avatarImage = $result[0];
356
-				break;
357
-			}
358
-		}
359
-
360
-		return $this->avatarImage;
361
-	}
362
-
363
-	/**
364
-	 * @brief marks the user as having logged in at least once
365
-	 * @return null
366
-	 */
367
-	public function markLogin() {
368
-		$this->config->setUserValue(
369
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
370
-	}
371
-
372
-	/**
373
-	 * @brief marks the time when user features like email have been updated
374
-	 * @return null
375
-	 */
376
-	public function markRefreshTime() {
377
-		$this->config->setUserValue(
378
-			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
379
-	}
380
-
381
-	/**
382
-	 * @brief checks whether user features needs to be updated again by
383
-	 * comparing the difference of time of the last refresh to now with the
384
-	 * desired interval
385
-	 * @return bool
386
-	 */
387
-	private function needsRefresh() {
388
-		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389
-			self::USER_PREFKEY_LASTREFRESH, 0);
390
-
391
-		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392
-			return false;
393
-		}
394
-		return  true;
395
-	}
396
-
397
-	/**
398
-	 * Stores a key-value pair in relation to this user
399
-	 *
400
-	 * @param string $key
401
-	 * @param string $value
402
-	 */
403
-	private function store($key, $value) {
404
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
405
-	}
406
-
407
-	/**
408
-	 * Composes the display name and stores it in the database. The final
409
-	 * display name is returned.
410
-	 *
411
-	 * @param string $displayName
412
-	 * @param string $displayName2
413
-	 * @returns string the effective display name
414
-	 */
415
-	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
-		$displayName2 = (string)$displayName2;
417
-		if($displayName2 !== '') {
418
-			$displayName .= ' (' . $displayName2 . ')';
419
-		}
420
-		$this->store('displayName', $displayName);
421
-		return $displayName;
422
-	}
423
-
424
-	/**
425
-	 * Stores the LDAP Username in the Database
426
-	 * @param string $userName
427
-	 */
428
-	public function storeLDAPUserName($userName) {
429
-		$this->store('uid', $userName);
430
-	}
431
-
432
-	/**
433
-	 * @brief checks whether an update method specified by feature was run
434
-	 * already. If not, it will marked like this, because it is expected that
435
-	 * the method will be run, when false is returned.
436
-	 * @param string $feature email | quota | avatar (can be extended)
437
-	 * @return bool
438
-	 */
439
-	private function wasRefreshed($feature) {
440
-		if(isset($this->refreshedFeatures[$feature])) {
441
-			return true;
442
-		}
443
-		$this->refreshedFeatures[$feature] = 1;
444
-		return false;
445
-	}
446
-
447
-	/**
448
-	 * fetches the email from LDAP and stores it as Nextcloud user value
449
-	 * @param string $valueFromLDAP if known, to save an LDAP read request
450
-	 * @return null
451
-	 */
452
-	public function updateEmail($valueFromLDAP = null) {
453
-		if($this->wasRefreshed('email')) {
454
-			return;
455
-		}
456
-		$email = (string)$valueFromLDAP;
457
-		if(is_null($valueFromLDAP)) {
458
-			$emailAttribute = $this->connection->ldapEmailAttribute;
459
-			if ($emailAttribute !== '') {
460
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
462
-					$email = (string)$aEmail[0];
463
-				}
464
-			}
465
-		}
466
-		if ($email !== '') {
467
-			$user = $this->userManager->get($this->uid);
468
-			if (!is_null($user)) {
469
-				$currentEmail = (string)$user->getEMailAddress();
470
-				if ($currentEmail !== $email) {
471
-					$user->setEMailAddress($email);
472
-				}
473
-			}
474
-		}
475
-	}
476
-
477
-	/**
478
-	 * Overall process goes as follow:
479
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
480
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
481
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
482
-	 * 4. check if the target user exists and set the quota for the user.
483
-	 *
484
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
485
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
486
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
487
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
488
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
489
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
490
-	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
491
-	 *
492
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
493
-	 * @param string $valueFromLDAP the quota attribute's value can be passed,
494
-	 * to save the readAttribute request
495
-	 * @return null
496
-	 */
497
-	public function updateQuota($valueFromLDAP = null) {
498
-		if($this->wasRefreshed('quota')) {
499
-			return;
500
-		}
501
-
502
-		$quotaAttribute = $this->connection->ldapQuotaAttribute;
503
-		$defaultQuota = $this->connection->ldapQuotaDefault;
504
-		if($quotaAttribute === '' && $defaultQuota === '') {
505
-			return;
506
-		}
507
-
508
-		$quota = false;
509
-		if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
510
-			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
511
-			if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
512
-				$quota = $aQuota[0];
513
-			} else {
514
-				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
515
-			}
516
-		} else if ($this->verifyQuotaValue($valueFromLDAP)) {
517
-			$quota = $valueFromLDAP;
518
-		} else {
519
-			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
520
-		}
521
-
522
-		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
523
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
524
-			$quota = $defaultQuota;
525
-		} else if($quota === false) {
526
-			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
527
-			return;
528
-		}
529
-
530
-		$targetUser = $this->userManager->get($this->uid);
531
-		if ($targetUser instanceof IUser) {
532
-			$targetUser->setQuota($quota);
533
-		} else {
534
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
535
-		}
536
-	}
537
-
538
-	private function verifyQuotaValue($quotaValue) {
539
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
540
-	}
541
-
542
-	/**
543
-	 * called by a post_login hook to save the avatar picture
544
-	 *
545
-	 * @param array $params
546
-	 */
547
-	public function updateAvatarPostLogin($params) {
548
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
549
-			$this->updateAvatar();
550
-		}
551
-	}
552
-
553
-	/**
554
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
555
-	 * @return null
556
-	 */
557
-	public function updateAvatar() {
558
-		if($this->wasRefreshed('avatar')) {
559
-			return;
560
-		}
561
-		$avatarImage = $this->getAvatarImage();
562
-		if($avatarImage === false) {
563
-			//not set, nothing left to do;
564
-			return;
565
-		}
566
-		$this->image->loadFromBase64(base64_encode($avatarImage));
567
-		$this->setOwnCloudAvatar();
568
-	}
569
-
570
-	/**
571
-	 * @brief sets an image as Nextcloud avatar
572
-	 * @return null
573
-	 */
574
-	private function setOwnCloudAvatar() {
575
-		if(!$this->image->valid()) {
576
-			$this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
577
-			return;
578
-		}
579
-		//make sure it is a square and not bigger than 128x128
580
-		$size = min(array($this->image->width(), $this->image->height(), 128));
581
-		if(!$this->image->centerCrop($size)) {
582
-			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
583
-			return;
584
-		}
585
-
586
-		if(!$this->fs->isLoaded()) {
587
-			$this->fs->setup($this->uid);
588
-		}
589
-
590
-		try {
591
-			$avatar = $this->avatarManager->getAvatar($this->uid);
592
-			$avatar->set($this->image);
593
-		} catch (\Exception $e) {
594
-			\OC::$server->getLogger()->logException($e, [
595
-				'message' => 'Could not set avatar for ' . $this->dn,
596
-				'level' => ILogger::INFO,
597
-				'app' => 'user_ldap',
598
-			]);
599
-		}
600
-	}
601
-
602
-	/**
603
-	 * called by a post_login hook to handle password expiry
604
-	 *
605
-	 * @param array $params
606
-	 */
607
-	public function handlePasswordExpiry($params) {
608
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
609
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
610
-			return;//password expiry handling disabled
611
-		}
612
-		$uid = $params['uid'];
613
-		if(isset($uid) && $uid === $this->getUsername()) {
614
-			//retrieve relevant user attributes
615
-			$result = $this->access->search('objectclass=*', array($this->dn), ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
128
+        if ($username === null) {
129
+            $log->log("uid for '$dn' must not be null!", ILogger::ERROR);
130
+            throw new \InvalidArgumentException('uid must not be null!');
131
+        } else if ($username === '') {
132
+            $log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
133
+            throw new \InvalidArgumentException('uid must not be an empty string!');
134
+        }
135
+
136
+        $this->access              = $access;
137
+        $this->connection          = $access->getConnection();
138
+        $this->config              = $config;
139
+        $this->fs                  = $fs;
140
+        $this->dn                  = $dn;
141
+        $this->uid                 = $username;
142
+        $this->image               = $image;
143
+        $this->log                 = $log;
144
+        $this->avatarManager       = $avatarManager;
145
+        $this->userManager         = $userManager;
146
+        $this->notificationManager = $notificationManager;
147
+
148
+        \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
149
+    }
150
+
151
+    /**
152
+     * @brief updates properties like email, quota or avatar provided by LDAP
153
+     * @return null
154
+     */
155
+    public function update() {
156
+        if(is_null($this->dn)) {
157
+            return null;
158
+        }
159
+
160
+        $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161
+                self::USER_PREFKEY_FIRSTLOGIN, 0);
162
+
163
+        if($this->needsRefresh()) {
164
+            $this->updateEmail();
165
+            $this->updateQuota();
166
+            if($hasLoggedIn !== 0) {
167
+                //we do not need to try it, when the user has not been logged in
168
+                //before, because the file system will not be ready.
169
+                $this->updateAvatar();
170
+                //in order to get an avatar as soon as possible, mark the user
171
+                //as refreshed only when updating the avatar did happen
172
+                $this->markRefreshTime();
173
+            }
174
+        }
175
+    }
176
+
177
+    /**
178
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
179
+     * @param array $ldapEntry the user entry as retrieved from LDAP
180
+     */
181
+    public function processAttributes($ldapEntry) {
182
+        $this->markRefreshTime();
183
+        //Quota
184
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
185
+        if(isset($ldapEntry[$attr])) {
186
+            $this->updateQuota($ldapEntry[$attr][0]);
187
+        } else {
188
+            if ($this->connection->ldapQuotaDefault !== '') {
189
+                $this->updateQuota();
190
+            }
191
+        }
192
+        unset($attr);
193
+
194
+        //displayName
195
+        $displayName = $displayName2 = '';
196
+        $attr = strtolower($this->connection->ldapUserDisplayName);
197
+        if(isset($ldapEntry[$attr])) {
198
+            $displayName = (string)$ldapEntry[$attr][0];
199
+        }
200
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
201
+        if(isset($ldapEntry[$attr])) {
202
+            $displayName2 = (string)$ldapEntry[$attr][0];
203
+        }
204
+        if ($displayName !== '') {
205
+            $this->composeAndStoreDisplayName($displayName);
206
+            $this->access->cacheUserDisplayName(
207
+                $this->getUsername(),
208
+                $displayName,
209
+                $displayName2
210
+            );
211
+        }
212
+        unset($attr);
213
+
214
+        //Email
215
+        //email must be stored after displayname, because it would cause a user
216
+        //change event that will trigger fetching the display name again
217
+        $attr = strtolower($this->connection->ldapEmailAttribute);
218
+        if(isset($ldapEntry[$attr])) {
219
+            $this->updateEmail($ldapEntry[$attr][0]);
220
+        }
221
+        unset($attr);
222
+
223
+        // LDAP Username, needed for s2s sharing
224
+        if(isset($ldapEntry['uid'])) {
225
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
226
+        } else if(isset($ldapEntry['samaccountname'])) {
227
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228
+        }
229
+
230
+        //homePath
231
+        if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
+            if(isset($ldapEntry[$attr])) {
234
+                $this->access->cacheUserHome(
235
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236
+            }
237
+        }
238
+
239
+        //memberOf groups
240
+        $cacheKey = 'getMemberOf'.$this->getUsername();
241
+        $groups = false;
242
+        if(isset($ldapEntry['memberof'])) {
243
+            $groups = $ldapEntry['memberof'];
244
+        }
245
+        $this->connection->writeToCache($cacheKey, $groups);
246
+
247
+        //Avatar
248
+        $attrs = array('jpegphoto', 'thumbnailphoto');
249
+        foreach ($attrs as $attr)  {
250
+            if(isset($ldapEntry[$attr])) {
251
+                $this->avatarImage = $ldapEntry[$attr][0];
252
+                // the call to the method that saves the avatar in the file
253
+                // system must be postponed after the login. It is to ensure
254
+                // external mounts are mounted properly (e.g. with login
255
+                // credentials from the session).
256
+                \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
257
+                break;
258
+            }
259
+        }
260
+    }
261
+
262
+    /**
263
+     * @brief returns the LDAP DN of the user
264
+     * @return string
265
+     */
266
+    public function getDN() {
267
+        return $this->dn;
268
+    }
269
+
270
+    /**
271
+     * @brief returns the Nextcloud internal username of the user
272
+     * @return string
273
+     */
274
+    public function getUsername() {
275
+        return $this->uid;
276
+    }
277
+
278
+    /**
279
+     * returns the home directory of the user if specified by LDAP settings
280
+     * @param string $valueFromLDAP
281
+     * @return bool|string
282
+     * @throws \Exception
283
+     */
284
+    public function getHomePath($valueFromLDAP = null) {
285
+        $path = (string)$valueFromLDAP;
286
+        $attr = null;
287
+
288
+        if (is_null($valueFromLDAP)
289
+           && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
290
+           && $this->access->connection->homeFolderNamingRule !== 'attr:')
291
+        {
292
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
293
+            $homedir = $this->access->readAttribute(
294
+                $this->access->username2dn($this->getUsername()), $attr);
295
+            if ($homedir && isset($homedir[0])) {
296
+                $path = $homedir[0];
297
+            }
298
+        }
299
+
300
+        if ($path !== '') {
301
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
302
+            //check for / at the beginning or pattern c:\ resp. c:/
303
+            if(   '/' !== $path[0]
304
+               && !(3 < strlen($path) && ctype_alpha($path[0])
305
+                   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306
+            ) {
307
+                $path = $this->config->getSystemValue('datadirectory',
308
+                        \OC::$SERVERROOT.'/data' ) . '/' . $path;
309
+            }
310
+            //we need it to store it in the DB as well in case a user gets
311
+            //deleted so we can clean up afterwards
312
+            $this->config->setUserValue(
313
+                $this->getUsername(), 'user_ldap', 'homePath', $path
314
+            );
315
+            return $path;
316
+        }
317
+
318
+        if(    !is_null($attr)
319
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320
+        ) {
321
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
323
+        }
324
+
325
+        //false will apply default behaviour as defined and done by OC_User
326
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
327
+        return false;
328
+    }
329
+
330
+    public function getMemberOfGroups() {
331
+        $cacheKey = 'getMemberOf'.$this->getUsername();
332
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
333
+        if(!is_null($memberOfGroups)) {
334
+            return $memberOfGroups;
335
+        }
336
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
337
+        $this->connection->writeToCache($cacheKey, $groupDNs);
338
+        return $groupDNs;
339
+    }
340
+
341
+    /**
342
+     * @brief reads the image from LDAP that shall be used as Avatar
343
+     * @return string data (provided by LDAP) | false
344
+     */
345
+    public function getAvatarImage() {
346
+        if(!is_null($this->avatarImage)) {
347
+            return $this->avatarImage;
348
+        }
349
+
350
+        $this->avatarImage = false;
351
+        $attributes = array('jpegPhoto', 'thumbnailPhoto');
352
+        foreach($attributes as $attribute) {
353
+            $result = $this->access->readAttribute($this->dn, $attribute);
354
+            if($result !== false && is_array($result) && isset($result[0])) {
355
+                $this->avatarImage = $result[0];
356
+                break;
357
+            }
358
+        }
359
+
360
+        return $this->avatarImage;
361
+    }
362
+
363
+    /**
364
+     * @brief marks the user as having logged in at least once
365
+     * @return null
366
+     */
367
+    public function markLogin() {
368
+        $this->config->setUserValue(
369
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
370
+    }
371
+
372
+    /**
373
+     * @brief marks the time when user features like email have been updated
374
+     * @return null
375
+     */
376
+    public function markRefreshTime() {
377
+        $this->config->setUserValue(
378
+            $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
379
+    }
380
+
381
+    /**
382
+     * @brief checks whether user features needs to be updated again by
383
+     * comparing the difference of time of the last refresh to now with the
384
+     * desired interval
385
+     * @return bool
386
+     */
387
+    private function needsRefresh() {
388
+        $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389
+            self::USER_PREFKEY_LASTREFRESH, 0);
390
+
391
+        if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392
+            return false;
393
+        }
394
+        return  true;
395
+    }
396
+
397
+    /**
398
+     * Stores a key-value pair in relation to this user
399
+     *
400
+     * @param string $key
401
+     * @param string $value
402
+     */
403
+    private function store($key, $value) {
404
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
405
+    }
406
+
407
+    /**
408
+     * Composes the display name and stores it in the database. The final
409
+     * display name is returned.
410
+     *
411
+     * @param string $displayName
412
+     * @param string $displayName2
413
+     * @returns string the effective display name
414
+     */
415
+    public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
+        $displayName2 = (string)$displayName2;
417
+        if($displayName2 !== '') {
418
+            $displayName .= ' (' . $displayName2 . ')';
419
+        }
420
+        $this->store('displayName', $displayName);
421
+        return $displayName;
422
+    }
423
+
424
+    /**
425
+     * Stores the LDAP Username in the Database
426
+     * @param string $userName
427
+     */
428
+    public function storeLDAPUserName($userName) {
429
+        $this->store('uid', $userName);
430
+    }
431
+
432
+    /**
433
+     * @brief checks whether an update method specified by feature was run
434
+     * already. If not, it will marked like this, because it is expected that
435
+     * the method will be run, when false is returned.
436
+     * @param string $feature email | quota | avatar (can be extended)
437
+     * @return bool
438
+     */
439
+    private function wasRefreshed($feature) {
440
+        if(isset($this->refreshedFeatures[$feature])) {
441
+            return true;
442
+        }
443
+        $this->refreshedFeatures[$feature] = 1;
444
+        return false;
445
+    }
446
+
447
+    /**
448
+     * fetches the email from LDAP and stores it as Nextcloud user value
449
+     * @param string $valueFromLDAP if known, to save an LDAP read request
450
+     * @return null
451
+     */
452
+    public function updateEmail($valueFromLDAP = null) {
453
+        if($this->wasRefreshed('email')) {
454
+            return;
455
+        }
456
+        $email = (string)$valueFromLDAP;
457
+        if(is_null($valueFromLDAP)) {
458
+            $emailAttribute = $this->connection->ldapEmailAttribute;
459
+            if ($emailAttribute !== '') {
460
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
+                if(is_array($aEmail) && (count($aEmail) > 0)) {
462
+                    $email = (string)$aEmail[0];
463
+                }
464
+            }
465
+        }
466
+        if ($email !== '') {
467
+            $user = $this->userManager->get($this->uid);
468
+            if (!is_null($user)) {
469
+                $currentEmail = (string)$user->getEMailAddress();
470
+                if ($currentEmail !== $email) {
471
+                    $user->setEMailAddress($email);
472
+                }
473
+            }
474
+        }
475
+    }
476
+
477
+    /**
478
+     * Overall process goes as follow:
479
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
480
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
481
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
482
+     * 4. check if the target user exists and set the quota for the user.
483
+     *
484
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
485
+     * parameter can be passed with the value of the attribute. This value will be considered as the
486
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
487
+     * fetch all the user's attributes in one call and use the fetched values in this function.
488
+     * The expected value for that parameter is a string describing the quota for the user. Valid
489
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
490
+     * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
491
+     *
492
+     * fetches the quota from LDAP and stores it as Nextcloud user value
493
+     * @param string $valueFromLDAP the quota attribute's value can be passed,
494
+     * to save the readAttribute request
495
+     * @return null
496
+     */
497
+    public function updateQuota($valueFromLDAP = null) {
498
+        if($this->wasRefreshed('quota')) {
499
+            return;
500
+        }
501
+
502
+        $quotaAttribute = $this->connection->ldapQuotaAttribute;
503
+        $defaultQuota = $this->connection->ldapQuotaDefault;
504
+        if($quotaAttribute === '' && $defaultQuota === '') {
505
+            return;
506
+        }
507
+
508
+        $quota = false;
509
+        if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
510
+            $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
511
+            if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
512
+                $quota = $aQuota[0];
513
+            } else {
514
+                $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
515
+            }
516
+        } else if ($this->verifyQuotaValue($valueFromLDAP)) {
517
+            $quota = $valueFromLDAP;
518
+        } else {
519
+            $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
520
+        }
521
+
522
+        if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
523
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
524
+            $quota = $defaultQuota;
525
+        } else if($quota === false) {
526
+            $this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
527
+            return;
528
+        }
529
+
530
+        $targetUser = $this->userManager->get($this->uid);
531
+        if ($targetUser instanceof IUser) {
532
+            $targetUser->setQuota($quota);
533
+        } else {
534
+            $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
535
+        }
536
+    }
537
+
538
+    private function verifyQuotaValue($quotaValue) {
539
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
540
+    }
541
+
542
+    /**
543
+     * called by a post_login hook to save the avatar picture
544
+     *
545
+     * @param array $params
546
+     */
547
+    public function updateAvatarPostLogin($params) {
548
+        if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
549
+            $this->updateAvatar();
550
+        }
551
+    }
552
+
553
+    /**
554
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
555
+     * @return null
556
+     */
557
+    public function updateAvatar() {
558
+        if($this->wasRefreshed('avatar')) {
559
+            return;
560
+        }
561
+        $avatarImage = $this->getAvatarImage();
562
+        if($avatarImage === false) {
563
+            //not set, nothing left to do;
564
+            return;
565
+        }
566
+        $this->image->loadFromBase64(base64_encode($avatarImage));
567
+        $this->setOwnCloudAvatar();
568
+    }
569
+
570
+    /**
571
+     * @brief sets an image as Nextcloud avatar
572
+     * @return null
573
+     */
574
+    private function setOwnCloudAvatar() {
575
+        if(!$this->image->valid()) {
576
+            $this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
577
+            return;
578
+        }
579
+        //make sure it is a square and not bigger than 128x128
580
+        $size = min(array($this->image->width(), $this->image->height(), 128));
581
+        if(!$this->image->centerCrop($size)) {
582
+            $this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
583
+            return;
584
+        }
585
+
586
+        if(!$this->fs->isLoaded()) {
587
+            $this->fs->setup($this->uid);
588
+        }
589
+
590
+        try {
591
+            $avatar = $this->avatarManager->getAvatar($this->uid);
592
+            $avatar->set($this->image);
593
+        } catch (\Exception $e) {
594
+            \OC::$server->getLogger()->logException($e, [
595
+                'message' => 'Could not set avatar for ' . $this->dn,
596
+                'level' => ILogger::INFO,
597
+                'app' => 'user_ldap',
598
+            ]);
599
+        }
600
+    }
601
+
602
+    /**
603
+     * called by a post_login hook to handle password expiry
604
+     *
605
+     * @param array $params
606
+     */
607
+    public function handlePasswordExpiry($params) {
608
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
609
+        if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
610
+            return;//password expiry handling disabled
611
+        }
612
+        $uid = $params['uid'];
613
+        if(isset($uid) && $uid === $this->getUsername()) {
614
+            //retrieve relevant user attributes
615
+            $result = $this->access->search('objectclass=*', array($this->dn), ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
616 616
 			
617
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
618
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
619
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
620
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
621
-				}
622
-			}
617
+            if(array_key_exists('pwdpolicysubentry', $result[0])) {
618
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
619
+                if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
620
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
621
+                }
622
+            }
623 623
 			
624
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
625
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
626
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
624
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
625
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
626
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
627 627
 			
628
-			//retrieve relevant password policy attributes
629
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
630
-			$result = $this->connection->getFromCache($cacheKey);
631
-			if(is_null($result)) {
632
-				$result = $this->access->search('objectclass=*', array($ppolicyDN), ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
633
-				$this->connection->writeToCache($cacheKey, $result);
634
-			}
628
+            //retrieve relevant password policy attributes
629
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
630
+            $result = $this->connection->getFromCache($cacheKey);
631
+            if(is_null($result)) {
632
+                $result = $this->access->search('objectclass=*', array($ppolicyDN), ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
633
+                $this->connection->writeToCache($cacheKey, $result);
634
+            }
635 635
 			
636
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
637
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
638
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
636
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
637
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
638
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
639 639
 			
640
-			//handle grace login
641
-			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
642
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
-				if($pwdGraceAuthNLimit 
644
-					&& (count($pwdGraceAuthNLimit) > 0)
645
-					&&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
646
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
647
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
648
-					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
649
-				} else { //no more grace login available
650
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
651
-					'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
652
-				}
653
-				exit();
654
-			}
655
-			//handle pwdReset attribute
656
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
657
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
658
-				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
659
-				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
660
-				exit();
661
-			}
662
-			//handle password expiry warning
663
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
665
-					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
666
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
667
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
668
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
669
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
670
-						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
671
-						$currentDateTime = new \DateTime();
672
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
673
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
674
-							//remove last password expiry warning if any
675
-							$notification = $this->notificationManager->createNotification();
676
-							$notification->setApp('user_ldap')
677
-								->setUser($uid)
678
-								->setObject('pwd_exp_warn', $uid)
679
-							;
680
-							$this->notificationManager->markProcessed($notification);
681
-							//create new password expiry warning
682
-							$notification = $this->notificationManager->createNotification();
683
-							$notification->setApp('user_ldap')
684
-								->setUser($uid)
685
-								->setDateTime($currentDateTime)
686
-								->setObject('pwd_exp_warn', $uid) 
687
-								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
688
-							;
689
-							$this->notificationManager->notify($notification);
690
-						}
691
-					}
692
-				}
693
-			}
694
-		}
695
-	}
640
+            //handle grace login
641
+            $pwdGraceUseTimeCount = count($pwdGraceUseTime);
642
+            if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
+                if($pwdGraceAuthNLimit 
644
+                    && (count($pwdGraceAuthNLimit) > 0)
645
+                    &&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
646
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
647
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
648
+                    'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
649
+                } else { //no more grace login available
650
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
651
+                    'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
652
+                }
653
+                exit();
654
+            }
655
+            //handle pwdReset attribute
656
+            if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
657
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
658
+                header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
659
+                'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
660
+                exit();
661
+            }
662
+            //handle password expiry warning
663
+            if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
+                if($pwdMaxAge && (count($pwdMaxAge) > 0)
665
+                    && $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
666
+                    $pwdMaxAgeInt = (int)$pwdMaxAge[0];
667
+                    $pwdExpireWarningInt = (int)$pwdExpireWarning[0];
668
+                    if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
669
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
670
+                        $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
671
+                        $currentDateTime = new \DateTime();
672
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
673
+                        if($secondsToExpiry <= $pwdExpireWarningInt) {
674
+                            //remove last password expiry warning if any
675
+                            $notification = $this->notificationManager->createNotification();
676
+                            $notification->setApp('user_ldap')
677
+                                ->setUser($uid)
678
+                                ->setObject('pwd_exp_warn', $uid)
679
+                            ;
680
+                            $this->notificationManager->markProcessed($notification);
681
+                            //create new password expiry warning
682
+                            $notification = $this->notificationManager->createNotification();
683
+                            $notification->setApp('user_ldap')
684
+                                ->setUser($uid)
685
+                                ->setDateTime($currentDateTime)
686
+                                ->setObject('pwd_exp_warn', $uid) 
687
+                                ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
688
+                            ;
689
+                            $this->notificationManager->notify($notification);
690
+                        }
691
+                    }
692
+                }
693
+            }
694
+        }
695
+    }
696 696
 }
Please login to merge, or discard this patch.
Spacing   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -153,17 +153,17 @@  discard block
 block discarded – undo
153 153
 	 * @return null
154 154
 	 */
155 155
 	public function update() {
156
-		if(is_null($this->dn)) {
156
+		if (is_null($this->dn)) {
157 157
 			return null;
158 158
 		}
159 159
 
160 160
 		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161 161
 				self::USER_PREFKEY_FIRSTLOGIN, 0);
162 162
 
163
-		if($this->needsRefresh()) {
163
+		if ($this->needsRefresh()) {
164 164
 			$this->updateEmail();
165 165
 			$this->updateQuota();
166
-			if($hasLoggedIn !== 0) {
166
+			if ($hasLoggedIn !== 0) {
167 167
 				//we do not need to try it, when the user has not been logged in
168 168
 				//before, because the file system will not be ready.
169 169
 				$this->updateAvatar();
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 		$this->markRefreshTime();
183 183
 		//Quota
184 184
 		$attr = strtolower($this->connection->ldapQuotaAttribute);
185
-		if(isset($ldapEntry[$attr])) {
185
+		if (isset($ldapEntry[$attr])) {
186 186
 			$this->updateQuota($ldapEntry[$attr][0]);
187 187
 		} else {
188 188
 			if ($this->connection->ldapQuotaDefault !== '') {
@@ -194,12 +194,12 @@  discard block
 block discarded – undo
194 194
 		//displayName
195 195
 		$displayName = $displayName2 = '';
196 196
 		$attr = strtolower($this->connection->ldapUserDisplayName);
197
-		if(isset($ldapEntry[$attr])) {
198
-			$displayName = (string)$ldapEntry[$attr][0];
197
+		if (isset($ldapEntry[$attr])) {
198
+			$displayName = (string) $ldapEntry[$attr][0];
199 199
 		}
200 200
 		$attr = strtolower($this->connection->ldapUserDisplayName2);
201
-		if(isset($ldapEntry[$attr])) {
202
-			$displayName2 = (string)$ldapEntry[$attr][0];
201
+		if (isset($ldapEntry[$attr])) {
202
+			$displayName2 = (string) $ldapEntry[$attr][0];
203 203
 		}
204 204
 		if ($displayName !== '') {
205 205
 			$this->composeAndStoreDisplayName($displayName);
@@ -215,22 +215,22 @@  discard block
 block discarded – undo
215 215
 		//email must be stored after displayname, because it would cause a user
216 216
 		//change event that will trigger fetching the display name again
217 217
 		$attr = strtolower($this->connection->ldapEmailAttribute);
218
-		if(isset($ldapEntry[$attr])) {
218
+		if (isset($ldapEntry[$attr])) {
219 219
 			$this->updateEmail($ldapEntry[$attr][0]);
220 220
 		}
221 221
 		unset($attr);
222 222
 
223 223
 		// LDAP Username, needed for s2s sharing
224
-		if(isset($ldapEntry['uid'])) {
224
+		if (isset($ldapEntry['uid'])) {
225 225
 			$this->storeLDAPUserName($ldapEntry['uid'][0]);
226
-		} else if(isset($ldapEntry['samaccountname'])) {
226
+		} else if (isset($ldapEntry['samaccountname'])) {
227 227
 			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228 228
 		}
229 229
 
230 230
 		//homePath
231
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
231
+		if (strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232 232
 			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
-			if(isset($ldapEntry[$attr])) {
233
+			if (isset($ldapEntry[$attr])) {
234 234
 				$this->access->cacheUserHome(
235 235
 					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236 236
 			}
@@ -239,15 +239,15 @@  discard block
 block discarded – undo
239 239
 		//memberOf groups
240 240
 		$cacheKey = 'getMemberOf'.$this->getUsername();
241 241
 		$groups = false;
242
-		if(isset($ldapEntry['memberof'])) {
242
+		if (isset($ldapEntry['memberof'])) {
243 243
 			$groups = $ldapEntry['memberof'];
244 244
 		}
245 245
 		$this->connection->writeToCache($cacheKey, $groups);
246 246
 
247 247
 		//Avatar
248 248
 		$attrs = array('jpegphoto', 'thumbnailphoto');
249
-		foreach ($attrs as $attr)  {
250
-			if(isset($ldapEntry[$attr])) {
249
+		foreach ($attrs as $attr) {
250
+			if (isset($ldapEntry[$attr])) {
251 251
 				$this->avatarImage = $ldapEntry[$attr][0];
252 252
 				// the call to the method that saves the avatar in the file
253 253
 				// system must be postponed after the login. It is to ensure
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
 	 * @throws \Exception
283 283
 	 */
284 284
 	public function getHomePath($valueFromLDAP = null) {
285
-		$path = (string)$valueFromLDAP;
285
+		$path = (string) $valueFromLDAP;
286 286
 		$attr = null;
287 287
 
288 288
 		if (is_null($valueFromLDAP)
@@ -300,12 +300,12 @@  discard block
 block discarded – undo
300 300
 		if ($path !== '') {
301 301
 			//if attribute's value is an absolute path take this, otherwise append it to data dir
302 302
 			//check for / at the beginning or pattern c:\ resp. c:/
303
-			if(   '/' !== $path[0]
303
+			if ('/' !== $path[0]
304 304
 			   && !(3 < strlen($path) && ctype_alpha($path[0])
305 305
 			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306 306
 			) {
307 307
 				$path = $this->config->getSystemValue('datadirectory',
308
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
308
+						\OC::$SERVERROOT.'/data').'/'.$path;
309 309
 			}
310 310
 			//we need it to store it in the DB as well in case a user gets
311 311
 			//deleted so we can clean up afterwards
@@ -315,11 +315,11 @@  discard block
 block discarded – undo
315 315
 			return $path;
316 316
 		}
317 317
 
318
-		if(    !is_null($attr)
318
+		if (!is_null($attr)
319 319
 			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320 320
 		) {
321 321
 			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
322
+			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: '.$this->getUsername());
323 323
 		}
324 324
 
325 325
 		//false will apply default behaviour as defined and done by OC_User
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 	public function getMemberOfGroups() {
331 331
 		$cacheKey = 'getMemberOf'.$this->getUsername();
332 332
 		$memberOfGroups = $this->connection->getFromCache($cacheKey);
333
-		if(!is_null($memberOfGroups)) {
333
+		if (!is_null($memberOfGroups)) {
334 334
 			return $memberOfGroups;
335 335
 		}
336 336
 		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
@@ -343,15 +343,15 @@  discard block
 block discarded – undo
343 343
 	 * @return string data (provided by LDAP) | false
344 344
 	 */
345 345
 	public function getAvatarImage() {
346
-		if(!is_null($this->avatarImage)) {
346
+		if (!is_null($this->avatarImage)) {
347 347
 			return $this->avatarImage;
348 348
 		}
349 349
 
350 350
 		$this->avatarImage = false;
351 351
 		$attributes = array('jpegPhoto', 'thumbnailPhoto');
352
-		foreach($attributes as $attribute) {
352
+		foreach ($attributes as $attribute) {
353 353
 			$result = $this->access->readAttribute($this->dn, $attribute);
354
-			if($result !== false && is_array($result) && isset($result[0])) {
354
+			if ($result !== false && is_array($result) && isset($result[0])) {
355 355
 				$this->avatarImage = $result[0];
356 356
 				break;
357 357
 			}
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
 		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389 389
 			self::USER_PREFKEY_LASTREFRESH, 0);
390 390
 
391
-		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
391
+		if ((time() - (int) $lastChecked) < (int) $this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392 392
 			return false;
393 393
 		}
394 394
 		return  true;
@@ -413,9 +413,9 @@  discard block
 block discarded – undo
413 413
 	 * @returns string the effective display name
414 414
 	 */
415 415
 	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
-		$displayName2 = (string)$displayName2;
417
-		if($displayName2 !== '') {
418
-			$displayName .= ' (' . $displayName2 . ')';
416
+		$displayName2 = (string) $displayName2;
417
+		if ($displayName2 !== '') {
418
+			$displayName .= ' ('.$displayName2.')';
419 419
 		}
420 420
 		$this->store('displayName', $displayName);
421 421
 		return $displayName;
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
 	 * @return bool
438 438
 	 */
439 439
 	private function wasRefreshed($feature) {
440
-		if(isset($this->refreshedFeatures[$feature])) {
440
+		if (isset($this->refreshedFeatures[$feature])) {
441 441
 			return true;
442 442
 		}
443 443
 		$this->refreshedFeatures[$feature] = 1;
@@ -450,23 +450,23 @@  discard block
 block discarded – undo
450 450
 	 * @return null
451 451
 	 */
452 452
 	public function updateEmail($valueFromLDAP = null) {
453
-		if($this->wasRefreshed('email')) {
453
+		if ($this->wasRefreshed('email')) {
454 454
 			return;
455 455
 		}
456
-		$email = (string)$valueFromLDAP;
457
-		if(is_null($valueFromLDAP)) {
456
+		$email = (string) $valueFromLDAP;
457
+		if (is_null($valueFromLDAP)) {
458 458
 			$emailAttribute = $this->connection->ldapEmailAttribute;
459 459
 			if ($emailAttribute !== '') {
460 460
 				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
462
-					$email = (string)$aEmail[0];
461
+				if (is_array($aEmail) && (count($aEmail) > 0)) {
462
+					$email = (string) $aEmail[0];
463 463
 				}
464 464
 			}
465 465
 		}
466 466
 		if ($email !== '') {
467 467
 			$user = $this->userManager->get($this->uid);
468 468
 			if (!is_null($user)) {
469
-				$currentEmail = (string)$user->getEMailAddress();
469
+				$currentEmail = (string) $user->getEMailAddress();
470 470
 				if ($currentEmail !== $email) {
471 471
 					$user->setEMailAddress($email);
472 472
 				}
@@ -495,35 +495,35 @@  discard block
 block discarded – undo
495 495
 	 * @return null
496 496
 	 */
497 497
 	public function updateQuota($valueFromLDAP = null) {
498
-		if($this->wasRefreshed('quota')) {
498
+		if ($this->wasRefreshed('quota')) {
499 499
 			return;
500 500
 		}
501 501
 
502 502
 		$quotaAttribute = $this->connection->ldapQuotaAttribute;
503 503
 		$defaultQuota = $this->connection->ldapQuotaDefault;
504
-		if($quotaAttribute === '' && $defaultQuota === '') {
504
+		if ($quotaAttribute === '' && $defaultQuota === '') {
505 505
 			return;
506 506
 		}
507 507
 
508 508
 		$quota = false;
509
-		if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
509
+		if (is_null($valueFromLDAP) && $quotaAttribute !== '') {
510 510
 			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
511
-			if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
511
+			if ($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
512 512
 				$quota = $aQuota[0];
513 513
 			} else {
514
-				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
514
+				$this->log->log('no suitable LDAP quota found for user '.$this->uid.': ['.$aQuota[0].']', ILogger::DEBUG);
515 515
 			}
516 516
 		} else if ($this->verifyQuotaValue($valueFromLDAP)) {
517 517
 			$quota = $valueFromLDAP;
518 518
 		} else {
519
-			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
519
+			$this->log->log('no suitable LDAP quota found for user '.$this->uid.': ['.$valueFromLDAP.']', ILogger::DEBUG);
520 520
 		}
521 521
 
522 522
 		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
523 523
 			// quota not found using the LDAP attribute (or not parseable). Try the default quota
524 524
 			$quota = $defaultQuota;
525
-		} else if($quota === false) {
526
-			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
525
+		} else if ($quota === false) {
526
+			$this->log->log('no suitable default quota found for user '.$this->uid.': ['.$defaultQuota.']', ILogger::DEBUG);
527 527
 			return;
528 528
 		}
529 529
 
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 		if ($targetUser instanceof IUser) {
532 532
 			$targetUser->setQuota($quota);
533 533
 		} else {
534
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
534
+			$this->log->log('trying to set a quota for user '.$this->uid.' but the user is missing', ILogger::INFO);
535 535
 		}
536 536
 	}
537 537
 
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
 	 * @param array $params
546 546
 	 */
547 547
 	public function updateAvatarPostLogin($params) {
548
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
548
+		if (isset($params['uid']) && $params['uid'] === $this->getUsername()) {
549 549
 			$this->updateAvatar();
550 550
 		}
551 551
 	}
@@ -555,11 +555,11 @@  discard block
 block discarded – undo
555 555
 	 * @return null
556 556
 	 */
557 557
 	public function updateAvatar() {
558
-		if($this->wasRefreshed('avatar')) {
558
+		if ($this->wasRefreshed('avatar')) {
559 559
 			return;
560 560
 		}
561 561
 		$avatarImage = $this->getAvatarImage();
562
-		if($avatarImage === false) {
562
+		if ($avatarImage === false) {
563 563
 			//not set, nothing left to do;
564 564
 			return;
565 565
 		}
@@ -572,18 +572,18 @@  discard block
 block discarded – undo
572 572
 	 * @return null
573 573
 	 */
574 574
 	private function setOwnCloudAvatar() {
575
-		if(!$this->image->valid()) {
575
+		if (!$this->image->valid()) {
576 576
 			$this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
577 577
 			return;
578 578
 		}
579 579
 		//make sure it is a square and not bigger than 128x128
580 580
 		$size = min(array($this->image->width(), $this->image->height(), 128));
581
-		if(!$this->image->centerCrop($size)) {
581
+		if (!$this->image->centerCrop($size)) {
582 582
 			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
583 583
 			return;
584 584
 		}
585 585
 
586
-		if(!$this->fs->isLoaded()) {
586
+		if (!$this->fs->isLoaded()) {
587 587
 			$this->fs->setup($this->uid);
588 588
 		}
589 589
 
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 			$avatar->set($this->image);
593 593
 		} catch (\Exception $e) {
594 594
 			\OC::$server->getLogger()->logException($e, [
595
-				'message' => 'Could not set avatar for ' . $this->dn,
595
+				'message' => 'Could not set avatar for '.$this->dn,
596 596
 				'level' => ILogger::INFO,
597 597
 				'app' => 'user_ldap',
598 598
 			]);
@@ -606,18 +606,18 @@  discard block
 block discarded – undo
606 606
 	 */
607 607
 	public function handlePasswordExpiry($params) {
608 608
 		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
609
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
610
-			return;//password expiry handling disabled
609
+		if (empty($ppolicyDN) || ((int) $this->connection->turnOnPasswordChange !== 1)) {
610
+			return; //password expiry handling disabled
611 611
 		}
612 612
 		$uid = $params['uid'];
613
-		if(isset($uid) && $uid === $this->getUsername()) {
613
+		if (isset($uid) && $uid === $this->getUsername()) {
614 614
 			//retrieve relevant user attributes
615 615
 			$result = $this->access->search('objectclass=*', array($this->dn), ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
616 616
 			
617
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
617
+			if (array_key_exists('pwdpolicysubentry', $result[0])) {
618 618
 				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
619
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
620
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
619
+				if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
620
+					$ppolicyDN = $pwdPolicySubentry[0]; //custom ppolicy DN
621 621
 				}
622 622
 			}
623 623
 			
@@ -626,9 +626,9 @@  discard block
 block discarded – undo
626 626
 			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
627 627
 			
628 628
 			//retrieve relevant password policy attributes
629
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
629
+			$cacheKey = 'ppolicyAttributes'.$ppolicyDN;
630 630
 			$result = $this->connection->getFromCache($cacheKey);
631
-			if(is_null($result)) {
631
+			if (is_null($result)) {
632 632
 				$result = $this->access->search('objectclass=*', array($ppolicyDN), ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
633 633
 				$this->connection->writeToCache($cacheKey, $result);
634 634
 			}
@@ -639,10 +639,10 @@  discard block
 block discarded – undo
639 639
 			
640 640
 			//handle grace login
641 641
 			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
642
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
-				if($pwdGraceAuthNLimit 
642
+			if ($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
643
+				if ($pwdGraceAuthNLimit 
644 644
 					&& (count($pwdGraceAuthNLimit) > 0)
645
-					&&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
645
+					&&($pwdGraceUseTimeCount < (int) $pwdGraceAuthNLimit[0])) { //at least one more grace login available?
646 646
 					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
647 647
 					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
648 648
 					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
@@ -653,24 +653,24 @@  discard block
 block discarded – undo
653 653
 				exit();
654 654
 			}
655 655
 			//handle pwdReset attribute
656
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
656
+			if ($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
657 657
 				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
658 658
 				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
659 659
 				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
660 660
 				exit();
661 661
 			}
662 662
 			//handle password expiry warning
663
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
663
+			if ($pwdChangedTime && (count($pwdChangedTime) > 0)) {
664
+				if ($pwdMaxAge && (count($pwdMaxAge) > 0)
665 665
 					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
666
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
667
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
668
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
666
+					$pwdMaxAgeInt = (int) $pwdMaxAge[0];
667
+					$pwdExpireWarningInt = (int) $pwdExpireWarning[0];
668
+					if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) {
669 669
 						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
670 670
 						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
671 671
 						$currentDateTime = new \DateTime();
672 672
 						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
673
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
673
+						if ($secondsToExpiry <= $pwdExpireWarningInt) {
674 674
 							//remove last password expiry warning if any
675 675
 							$notification = $this->notificationManager->createNotification();
676 676
 							$notification->setApp('user_ldap')
Please login to merge, or discard this patch.
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +618 added lines, -618 removed lines patch added patch discarded remove patch
@@ -62,623 +62,623 @@
 block discarded – undo
62 62
  * @property string ldapEmailAttribute
63 63
  */
64 64
 class Connection extends LDAPUtility {
65
-	private $ldapConnectionRes = null;
66
-	private $configPrefix;
67
-	private $configID;
68
-	private $configured = false;
69
-	private $hasPagedResultSupport = true;
70
-	//whether connection should be kept on __destruct
71
-	private $dontDestruct = false;
72
-
73
-	/**
74
-	 * @var bool runtime flag that indicates whether supported primary groups are available
75
-	 */
76
-	public $hasPrimaryGroups = true;
77
-
78
-	/**
79
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
80
-	 */
81
-	public $hasGidNumber = true;
82
-
83
-	//cache handler
84
-	protected $cache;
85
-
86
-	/** @var Configuration settings handler **/
87
-	protected $configuration;
88
-
89
-	protected $doNotValidate = false;
90
-
91
-	protected $ignoreValidation = false;
92
-
93
-	protected $bindResult = [];
94
-
95
-	/**
96
-	 * Constructor
97
-	 * @param ILDAPWrapper $ldap
98
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
99
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
100
-	 */
101
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
102
-		parent::__construct($ldap);
103
-		$this->configPrefix = $configPrefix;
104
-		$this->configID = $configID;
105
-		$this->configuration = new Configuration($configPrefix,
106
-												 !is_null($configID));
107
-		$memcache = \OC::$server->getMemCacheFactory();
108
-		if($memcache->isAvailable()) {
109
-			$this->cache = $memcache->createDistributed();
110
-		}
111
-		$helper = new Helper(\OC::$server->getConfig());
112
-		$this->doNotValidate = !in_array($this->configPrefix,
113
-			$helper->getServerConfigurationPrefixes());
114
-		$this->hasPagedResultSupport =
115
-			(int)$this->configuration->ldapPagingSize !== 0
116
-			|| $this->ldap->hasPagedResultSupport();
117
-	}
118
-
119
-	public function __destruct() {
120
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
121
-			@$this->ldap->unbind($this->ldapConnectionRes);
122
-			$this->bindResult = [];
123
-		}
124
-	}
125
-
126
-	/**
127
-	 * defines behaviour when the instance is cloned
128
-	 */
129
-	public function __clone() {
130
-		$this->configuration = new Configuration($this->configPrefix,
131
-												 !is_null($this->configID));
132
-		if(count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
133
-			$this->bindResult = [];
134
-		}
135
-		$this->ldapConnectionRes = null;
136
-		$this->dontDestruct = true;
137
-	}
138
-
139
-	/**
140
-	 * @param string $name
141
-	 * @return bool|mixed
142
-	 */
143
-	public function __get($name) {
144
-		if(!$this->configured) {
145
-			$this->readConfiguration();
146
-		}
147
-
148
-		if($name === 'hasPagedResultSupport') {
149
-			return $this->hasPagedResultSupport;
150
-		}
151
-
152
-		return $this->configuration->$name;
153
-	}
154
-
155
-	/**
156
-	 * @param string $name
157
-	 * @param mixed $value
158
-	 */
159
-	public function __set($name, $value) {
160
-		$this->doNotValidate = false;
161
-		$before = $this->configuration->$name;
162
-		$this->configuration->$name = $value;
163
-		$after = $this->configuration->$name;
164
-		if($before !== $after) {
165
-			if ($this->configID !== '' && $this->configID !== null) {
166
-				$this->configuration->saveConfiguration();
167
-			}
168
-			$this->validateConfiguration();
169
-		}
170
-	}
171
-
172
-	/**
173
-	 * sets whether the result of the configuration validation shall
174
-	 * be ignored when establishing the connection. Used by the Wizard
175
-	 * in early configuration state.
176
-	 * @param bool $state
177
-	 */
178
-	public function setIgnoreValidation($state) {
179
-		$this->ignoreValidation = (bool)$state;
180
-	}
181
-
182
-	/**
183
-	 * initializes the LDAP backend
184
-	 * @param bool $force read the config settings no matter what
185
-	 */
186
-	public function init($force = false) {
187
-		$this->readConfiguration($force);
188
-		$this->establishConnection();
189
-	}
190
-
191
-	/**
192
-	 * Returns the LDAP handler
193
-	 */
194
-	public function getConnectionResource() {
195
-		if(!$this->ldapConnectionRes) {
196
-			$this->init();
197
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
198
-			$this->ldapConnectionRes = null;
199
-			$this->establishConnection();
200
-		}
201
-		if(is_null($this->ldapConnectionRes)) {
202
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
203
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
204
-		}
205
-		return $this->ldapConnectionRes;
206
-	}
207
-
208
-	/**
209
-	 * resets the connection resource
210
-	 */
211
-	public function resetConnectionResource() {
212
-		if(!is_null($this->ldapConnectionRes)) {
213
-			@$this->ldap->unbind($this->ldapConnectionRes);
214
-			$this->ldapConnectionRes = null;
215
-			$this->bindResult = [];
216
-		}
217
-	}
218
-
219
-	/**
220
-	 * @param string|null $key
221
-	 * @return string
222
-	 */
223
-	private function getCacheKey($key) {
224
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
225
-		if(is_null($key)) {
226
-			return $prefix;
227
-		}
228
-		return $prefix.hash('sha256', $key);
229
-	}
230
-
231
-	/**
232
-	 * @param string $key
233
-	 * @return mixed|null
234
-	 */
235
-	public function getFromCache($key) {
236
-		if(!$this->configured) {
237
-			$this->readConfiguration();
238
-		}
239
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
240
-			return null;
241
-		}
242
-		$key = $this->getCacheKey($key);
243
-
244
-		return json_decode(base64_decode($this->cache->get($key)), true);
245
-	}
246
-
247
-	/**
248
-	 * @param string $key
249
-	 * @param mixed $value
250
-	 *
251
-	 * @return string
252
-	 */
253
-	public function writeToCache($key, $value) {
254
-		if(!$this->configured) {
255
-			$this->readConfiguration();
256
-		}
257
-		if(is_null($this->cache)
258
-			|| !$this->configuration->ldapCacheTTL
259
-			|| !$this->configuration->ldapConfigurationActive) {
260
-			return null;
261
-		}
262
-		$key   = $this->getCacheKey($key);
263
-		$value = base64_encode(json_encode($value));
264
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
265
-	}
266
-
267
-	public function clearCache() {
268
-		if(!is_null($this->cache)) {
269
-			$this->cache->clear($this->getCacheKey(null));
270
-		}
271
-	}
272
-
273
-	/**
274
-	 * Caches the general LDAP configuration.
275
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
276
-	 * to false.
277
-	 * @return null
278
-	 */
279
-	private function readConfiguration($force = false) {
280
-		if((!$this->configured || $force) && !is_null($this->configID)) {
281
-			$this->configuration->readConfiguration();
282
-			$this->configured = $this->validateConfiguration();
283
-		}
284
-	}
285
-
286
-	/**
287
-	 * set LDAP configuration with values delivered by an array, not read from configuration
288
-	 * @param array $config array that holds the config parameters in an associated array
289
-	 * @param array &$setParameters optional; array where the set fields will be given to
290
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
291
-	 */
292
-	public function setConfiguration($config, &$setParameters = null) {
293
-		if(is_null($setParameters)) {
294
-			$setParameters = array();
295
-		}
296
-		$this->doNotValidate = false;
297
-		$this->configuration->setConfiguration($config, $setParameters);
298
-		if(count($setParameters) > 0) {
299
-			$this->configured = $this->validateConfiguration();
300
-		}
301
-
302
-
303
-		return $this->configured;
304
-	}
305
-
306
-	/**
307
-	 * saves the current Configuration in the database and empties the
308
-	 * cache
309
-	 * @return null
310
-	 */
311
-	public function saveConfiguration() {
312
-		$this->configuration->saveConfiguration();
313
-		$this->clearCache();
314
-	}
315
-
316
-	/**
317
-	 * get the current LDAP configuration
318
-	 * @return array
319
-	 */
320
-	public function getConfiguration() {
321
-		$this->readConfiguration();
322
-		$config = $this->configuration->getConfiguration();
323
-		$cta = $this->configuration->getConfigTranslationArray();
324
-		$result = array();
325
-		foreach($cta as $dbkey => $configkey) {
326
-			switch($configkey) {
327
-				case 'homeFolderNamingRule':
328
-					if(strpos($config[$configkey], 'attr:') === 0) {
329
-						$result[$dbkey] = substr($config[$configkey], 5);
330
-					} else {
331
-						$result[$dbkey] = '';
332
-					}
333
-					break;
334
-				case 'ldapBase':
335
-				case 'ldapBaseUsers':
336
-				case 'ldapBaseGroups':
337
-				case 'ldapAttributesForUserSearch':
338
-				case 'ldapAttributesForGroupSearch':
339
-					if(is_array($config[$configkey])) {
340
-						$result[$dbkey] = implode("\n", $config[$configkey]);
341
-						break;
342
-					} //else follows default
343
-				default:
344
-					$result[$dbkey] = $config[$configkey];
345
-			}
346
-		}
347
-		return $result;
348
-	}
349
-
350
-	private function doSoftValidation() {
351
-		//if User or Group Base are not set, take over Base DN setting
352
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
353
-			$val = $this->configuration->$keyBase;
354
-			if(empty($val)) {
355
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
356
-			}
357
-		}
358
-
359
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
360
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
361
-				as $expertSetting => $effectiveSetting) {
362
-			$uuidOverride = $this->configuration->$expertSetting;
363
-			if(!empty($uuidOverride)) {
364
-				$this->configuration->$effectiveSetting = $uuidOverride;
365
-			} else {
366
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
367
-				array_unshift($uuidAttributes, 'auto');
368
-				if(!in_array($this->configuration->$effectiveSetting,
369
-							$uuidAttributes)
370
-					&& (!is_null($this->configID))) {
371
-					$this->configuration->$effectiveSetting = 'auto';
372
-					$this->configuration->saveConfiguration();
373
-					\OCP\Util::writeLog('user_ldap',
374
-										'Illegal value for the '.
375
-										$effectiveSetting.', '.'reset to '.
376
-										'autodetect.', ILogger::INFO);
377
-				}
378
-
379
-			}
380
-		}
381
-
382
-		$backupPort = (int)$this->configuration->ldapBackupPort;
383
-		if ($backupPort <= 0) {
384
-			$this->configuration->backupPort = $this->configuration->ldapPort;
385
-		}
386
-
387
-		//make sure empty search attributes are saved as simple, empty array
388
-		$saKeys = array('ldapAttributesForUserSearch',
389
-						'ldapAttributesForGroupSearch');
390
-		foreach($saKeys as $key) {
391
-			$val = $this->configuration->$key;
392
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
393
-				$this->configuration->$key = array();
394
-			}
395
-		}
396
-
397
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
398
-			&& $this->configuration->ldapTLS) {
399
-			$this->configuration->ldapTLS = false;
400
-			\OCP\Util::writeLog(
401
-				'user_ldap',
402
-				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
403
-				ILogger::INFO
404
-			);
405
-		}
406
-	}
407
-
408
-	/**
409
-	 * @return bool
410
-	 */
411
-	private function doCriticalValidation() {
412
-		$configurationOK = true;
413
-		$errorStr = 'Configuration Error (prefix '.
414
-			(string)$this->configPrefix .'): ';
415
-
416
-		//options that shall not be empty
417
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
418
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
419
-		foreach($options as $key) {
420
-			$val = $this->configuration->$key;
421
-			if(empty($val)) {
422
-				switch($key) {
423
-					case 'ldapHost':
424
-						$subj = 'LDAP Host';
425
-						break;
426
-					case 'ldapPort':
427
-						$subj = 'LDAP Port';
428
-						break;
429
-					case 'ldapUserDisplayName':
430
-						$subj = 'LDAP User Display Name';
431
-						break;
432
-					case 'ldapGroupDisplayName':
433
-						$subj = 'LDAP Group Display Name';
434
-						break;
435
-					case 'ldapLoginFilter':
436
-						$subj = 'LDAP Login Filter';
437
-						break;
438
-					default:
439
-						$subj = $key;
440
-						break;
441
-				}
442
-				$configurationOK = false;
443
-				\OCP\Util::writeLog(
444
-					'user_ldap',
445
-					$errorStr.'No '.$subj.' given!',
446
-					ILogger::WARN
447
-				);
448
-			}
449
-		}
450
-
451
-		//combinations
452
-		$agent = $this->configuration->ldapAgentName;
453
-		$pwd = $this->configuration->ldapAgentPassword;
454
-		if (
455
-			($agent === ''  && $pwd !== '')
456
-			|| ($agent !== '' && $pwd === '')
457
-		) {
458
-			\OCP\Util::writeLog(
459
-				'user_ldap',
460
-				$errorStr.'either no password is given for the user ' .
461
-					'agent or a password is given, but not an LDAP agent.',
462
-				ILogger::WARN);
463
-			$configurationOK = false;
464
-		}
465
-
466
-		$base = $this->configuration->ldapBase;
467
-		$baseUsers = $this->configuration->ldapBaseUsers;
468
-		$baseGroups = $this->configuration->ldapBaseGroups;
469
-
470
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
471
-			\OCP\Util::writeLog(
472
-				'user_ldap',
473
-				$errorStr.'Not a single Base DN given.',
474
-				ILogger::WARN
475
-			);
476
-			$configurationOK = false;
477
-		}
478
-
479
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
480
-		   === false) {
481
-			\OCP\Util::writeLog(
482
-				'user_ldap',
483
-				$errorStr.'login filter does not contain %uid place holder.',
484
-				ILogger::WARN
485
-			);
486
-			$configurationOK = false;
487
-		}
488
-
489
-		return $configurationOK;
490
-	}
491
-
492
-	/**
493
-	 * Validates the user specified configuration
494
-	 * @return bool true if configuration seems OK, false otherwise
495
-	 */
496
-	private function validateConfiguration() {
497
-
498
-		if($this->doNotValidate) {
499
-			//don't do a validation if it is a new configuration with pure
500
-			//default values. Will be allowed on changes via __set or
501
-			//setConfiguration
502
-			return false;
503
-		}
504
-
505
-		// first step: "soft" checks: settings that are not really
506
-		// necessary, but advisable. If left empty, give an info message
507
-		$this->doSoftValidation();
508
-
509
-		//second step: critical checks. If left empty or filled wrong, mark as
510
-		//not configured and give a warning.
511
-		return $this->doCriticalValidation();
512
-	}
513
-
514
-
515
-	/**
516
-	 * Connects and Binds to LDAP
517
-	 */
518
-	private function establishConnection() {
519
-		if(!$this->configuration->ldapConfigurationActive) {
520
-			return null;
521
-		}
522
-		static $phpLDAPinstalled = true;
523
-		if(!$phpLDAPinstalled) {
524
-			return false;
525
-		}
526
-		if(!$this->ignoreValidation && !$this->configured) {
527
-			\OCP\Util::writeLog(
528
-				'user_ldap',
529
-				'Configuration is invalid, cannot connect',
530
-				ILogger::WARN
531
-			);
532
-			return false;
533
-		}
534
-		if(!$this->ldapConnectionRes) {
535
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
536
-				$phpLDAPinstalled = false;
537
-				\OCP\Util::writeLog(
538
-					'user_ldap',
539
-					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
540
-					ILogger::ERROR
541
-				);
542
-
543
-				return false;
544
-			}
545
-			if($this->configuration->turnOffCertCheck) {
546
-				if(putenv('LDAPTLS_REQCERT=never')) {
547
-					\OCP\Util::writeLog('user_ldap',
548
-						'Turned off SSL certificate validation successfully.',
549
-						ILogger::DEBUG);
550
-				} else {
551
-					\OCP\Util::writeLog(
552
-						'user_ldap',
553
-						'Could not turn off SSL certificate validation.',
554
-						ILogger::WARN
555
-					);
556
-				}
557
-			}
558
-
559
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
560
-				|| $this->getFromCache('overrideMainServer'));
561
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
562
-			$bindStatus = false;
563
-			$error = -1;
564
-			try {
565
-				if (!$isOverrideMainServer) {
566
-					$this->doConnect($this->configuration->ldapHost,
567
-						$this->configuration->ldapPort);
568
-					$bindStatus = $this->bind();
569
-					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
570
-						$this->ldap->errno($this->ldapConnectionRes) : -1;
571
-				}
572
-				if($bindStatus === true) {
573
-					return $bindStatus;
574
-				}
575
-			} catch (ServerNotAvailableException $e) {
576
-				if(!$isBackupHost) {
577
-					throw $e;
578
-				}
579
-			}
580
-
581
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
582
-			if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
583
-				$this->doConnect($this->configuration->ldapBackupHost,
584
-								 $this->configuration->ldapBackupPort);
585
-				$this->bindResult = [];
586
-				$bindStatus = $this->bind();
587
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
588
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
589
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
590
-					//when bind to backup server succeeded and failed to main server,
591
-					//skip contacting him until next cache refresh
592
-					$this->writeToCache('overrideMainServer', true);
593
-				}
594
-			}
595
-
596
-			return $bindStatus;
597
-		}
598
-		return null;
599
-	}
600
-
601
-	/**
602
-	 * @param string $host
603
-	 * @param string $port
604
-	 * @return bool
605
-	 * @throws \OC\ServerNotAvailableException
606
-	 */
607
-	private function doConnect($host, $port) {
608
-		if ($host === '') {
609
-			return false;
610
-		}
611
-
612
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
613
-
614
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
615
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
616
-		}
617
-
618
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
619
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
620
-		}
621
-
622
-		if($this->configuration->ldapTLS) {
623
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
624
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
625
-			}
626
-		}
627
-
628
-		return true;
629
-	}
630
-
631
-	/**
632
-	 * Binds to LDAP
633
-	 */
634
-	public function bind() {
635
-		if(!$this->configuration->ldapConfigurationActive) {
636
-			return false;
637
-		}
638
-		$cr = $this->ldapConnectionRes;
639
-		if(!$this->ldap->isResource($cr)) {
640
-			$cr = $this->getConnectionResource();
641
-		}
642
-
643
-		if(
644
-			count($this->bindResult) !== 0
645
-			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
646
-			&& \OC::$server->getHasher()->verify(
647
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
648
-				$this->bindResult['hash']
649
-			)
650
-		) {
651
-			// don't attempt to bind again with the same data as before
652
-			// bind might have been invoked via getConnectionResource(),
653
-			// but we need results specifically for e.g. user login
654
-			return $this->bindResult['result'];
655
-		}
656
-
657
-		$ldapLogin = @$this->ldap->bind($cr,
658
-										$this->configuration->ldapAgentName,
659
-										$this->configuration->ldapAgentPassword);
660
-
661
-		$this->bindResult = [
662
-			'dn' => $this->configuration->ldapAgentName,
663
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
664
-			'result' => $ldapLogin,
665
-		];
666
-
667
-		if(!$ldapLogin) {
668
-			$errno = $this->ldap->errno($cr);
669
-
670
-			\OCP\Util::writeLog('user_ldap',
671
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
672
-				ILogger::WARN);
673
-
674
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
675
-			if($errno !== 0x00 && $errno !== 0x31) {
676
-				$this->ldapConnectionRes = null;
677
-			}
678
-
679
-			return false;
680
-		}
681
-		return true;
682
-	}
65
+    private $ldapConnectionRes = null;
66
+    private $configPrefix;
67
+    private $configID;
68
+    private $configured = false;
69
+    private $hasPagedResultSupport = true;
70
+    //whether connection should be kept on __destruct
71
+    private $dontDestruct = false;
72
+
73
+    /**
74
+     * @var bool runtime flag that indicates whether supported primary groups are available
75
+     */
76
+    public $hasPrimaryGroups = true;
77
+
78
+    /**
79
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
80
+     */
81
+    public $hasGidNumber = true;
82
+
83
+    //cache handler
84
+    protected $cache;
85
+
86
+    /** @var Configuration settings handler **/
87
+    protected $configuration;
88
+
89
+    protected $doNotValidate = false;
90
+
91
+    protected $ignoreValidation = false;
92
+
93
+    protected $bindResult = [];
94
+
95
+    /**
96
+     * Constructor
97
+     * @param ILDAPWrapper $ldap
98
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
99
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
100
+     */
101
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
102
+        parent::__construct($ldap);
103
+        $this->configPrefix = $configPrefix;
104
+        $this->configID = $configID;
105
+        $this->configuration = new Configuration($configPrefix,
106
+                                                    !is_null($configID));
107
+        $memcache = \OC::$server->getMemCacheFactory();
108
+        if($memcache->isAvailable()) {
109
+            $this->cache = $memcache->createDistributed();
110
+        }
111
+        $helper = new Helper(\OC::$server->getConfig());
112
+        $this->doNotValidate = !in_array($this->configPrefix,
113
+            $helper->getServerConfigurationPrefixes());
114
+        $this->hasPagedResultSupport =
115
+            (int)$this->configuration->ldapPagingSize !== 0
116
+            || $this->ldap->hasPagedResultSupport();
117
+    }
118
+
119
+    public function __destruct() {
120
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
121
+            @$this->ldap->unbind($this->ldapConnectionRes);
122
+            $this->bindResult = [];
123
+        }
124
+    }
125
+
126
+    /**
127
+     * defines behaviour when the instance is cloned
128
+     */
129
+    public function __clone() {
130
+        $this->configuration = new Configuration($this->configPrefix,
131
+                                                    !is_null($this->configID));
132
+        if(count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
133
+            $this->bindResult = [];
134
+        }
135
+        $this->ldapConnectionRes = null;
136
+        $this->dontDestruct = true;
137
+    }
138
+
139
+    /**
140
+     * @param string $name
141
+     * @return bool|mixed
142
+     */
143
+    public function __get($name) {
144
+        if(!$this->configured) {
145
+            $this->readConfiguration();
146
+        }
147
+
148
+        if($name === 'hasPagedResultSupport') {
149
+            return $this->hasPagedResultSupport;
150
+        }
151
+
152
+        return $this->configuration->$name;
153
+    }
154
+
155
+    /**
156
+     * @param string $name
157
+     * @param mixed $value
158
+     */
159
+    public function __set($name, $value) {
160
+        $this->doNotValidate = false;
161
+        $before = $this->configuration->$name;
162
+        $this->configuration->$name = $value;
163
+        $after = $this->configuration->$name;
164
+        if($before !== $after) {
165
+            if ($this->configID !== '' && $this->configID !== null) {
166
+                $this->configuration->saveConfiguration();
167
+            }
168
+            $this->validateConfiguration();
169
+        }
170
+    }
171
+
172
+    /**
173
+     * sets whether the result of the configuration validation shall
174
+     * be ignored when establishing the connection. Used by the Wizard
175
+     * in early configuration state.
176
+     * @param bool $state
177
+     */
178
+    public function setIgnoreValidation($state) {
179
+        $this->ignoreValidation = (bool)$state;
180
+    }
181
+
182
+    /**
183
+     * initializes the LDAP backend
184
+     * @param bool $force read the config settings no matter what
185
+     */
186
+    public function init($force = false) {
187
+        $this->readConfiguration($force);
188
+        $this->establishConnection();
189
+    }
190
+
191
+    /**
192
+     * Returns the LDAP handler
193
+     */
194
+    public function getConnectionResource() {
195
+        if(!$this->ldapConnectionRes) {
196
+            $this->init();
197
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
198
+            $this->ldapConnectionRes = null;
199
+            $this->establishConnection();
200
+        }
201
+        if(is_null($this->ldapConnectionRes)) {
202
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
203
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
204
+        }
205
+        return $this->ldapConnectionRes;
206
+    }
207
+
208
+    /**
209
+     * resets the connection resource
210
+     */
211
+    public function resetConnectionResource() {
212
+        if(!is_null($this->ldapConnectionRes)) {
213
+            @$this->ldap->unbind($this->ldapConnectionRes);
214
+            $this->ldapConnectionRes = null;
215
+            $this->bindResult = [];
216
+        }
217
+    }
218
+
219
+    /**
220
+     * @param string|null $key
221
+     * @return string
222
+     */
223
+    private function getCacheKey($key) {
224
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
225
+        if(is_null($key)) {
226
+            return $prefix;
227
+        }
228
+        return $prefix.hash('sha256', $key);
229
+    }
230
+
231
+    /**
232
+     * @param string $key
233
+     * @return mixed|null
234
+     */
235
+    public function getFromCache($key) {
236
+        if(!$this->configured) {
237
+            $this->readConfiguration();
238
+        }
239
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
240
+            return null;
241
+        }
242
+        $key = $this->getCacheKey($key);
243
+
244
+        return json_decode(base64_decode($this->cache->get($key)), true);
245
+    }
246
+
247
+    /**
248
+     * @param string $key
249
+     * @param mixed $value
250
+     *
251
+     * @return string
252
+     */
253
+    public function writeToCache($key, $value) {
254
+        if(!$this->configured) {
255
+            $this->readConfiguration();
256
+        }
257
+        if(is_null($this->cache)
258
+            || !$this->configuration->ldapCacheTTL
259
+            || !$this->configuration->ldapConfigurationActive) {
260
+            return null;
261
+        }
262
+        $key   = $this->getCacheKey($key);
263
+        $value = base64_encode(json_encode($value));
264
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
265
+    }
266
+
267
+    public function clearCache() {
268
+        if(!is_null($this->cache)) {
269
+            $this->cache->clear($this->getCacheKey(null));
270
+        }
271
+    }
272
+
273
+    /**
274
+     * Caches the general LDAP configuration.
275
+     * @param bool $force optional. true, if the re-read should be forced. defaults
276
+     * to false.
277
+     * @return null
278
+     */
279
+    private function readConfiguration($force = false) {
280
+        if((!$this->configured || $force) && !is_null($this->configID)) {
281
+            $this->configuration->readConfiguration();
282
+            $this->configured = $this->validateConfiguration();
283
+        }
284
+    }
285
+
286
+    /**
287
+     * set LDAP configuration with values delivered by an array, not read from configuration
288
+     * @param array $config array that holds the config parameters in an associated array
289
+     * @param array &$setParameters optional; array where the set fields will be given to
290
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
291
+     */
292
+    public function setConfiguration($config, &$setParameters = null) {
293
+        if(is_null($setParameters)) {
294
+            $setParameters = array();
295
+        }
296
+        $this->doNotValidate = false;
297
+        $this->configuration->setConfiguration($config, $setParameters);
298
+        if(count($setParameters) > 0) {
299
+            $this->configured = $this->validateConfiguration();
300
+        }
301
+
302
+
303
+        return $this->configured;
304
+    }
305
+
306
+    /**
307
+     * saves the current Configuration in the database and empties the
308
+     * cache
309
+     * @return null
310
+     */
311
+    public function saveConfiguration() {
312
+        $this->configuration->saveConfiguration();
313
+        $this->clearCache();
314
+    }
315
+
316
+    /**
317
+     * get the current LDAP configuration
318
+     * @return array
319
+     */
320
+    public function getConfiguration() {
321
+        $this->readConfiguration();
322
+        $config = $this->configuration->getConfiguration();
323
+        $cta = $this->configuration->getConfigTranslationArray();
324
+        $result = array();
325
+        foreach($cta as $dbkey => $configkey) {
326
+            switch($configkey) {
327
+                case 'homeFolderNamingRule':
328
+                    if(strpos($config[$configkey], 'attr:') === 0) {
329
+                        $result[$dbkey] = substr($config[$configkey], 5);
330
+                    } else {
331
+                        $result[$dbkey] = '';
332
+                    }
333
+                    break;
334
+                case 'ldapBase':
335
+                case 'ldapBaseUsers':
336
+                case 'ldapBaseGroups':
337
+                case 'ldapAttributesForUserSearch':
338
+                case 'ldapAttributesForGroupSearch':
339
+                    if(is_array($config[$configkey])) {
340
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
341
+                        break;
342
+                    } //else follows default
343
+                default:
344
+                    $result[$dbkey] = $config[$configkey];
345
+            }
346
+        }
347
+        return $result;
348
+    }
349
+
350
+    private function doSoftValidation() {
351
+        //if User or Group Base are not set, take over Base DN setting
352
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
353
+            $val = $this->configuration->$keyBase;
354
+            if(empty($val)) {
355
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
356
+            }
357
+        }
358
+
359
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
360
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
361
+                as $expertSetting => $effectiveSetting) {
362
+            $uuidOverride = $this->configuration->$expertSetting;
363
+            if(!empty($uuidOverride)) {
364
+                $this->configuration->$effectiveSetting = $uuidOverride;
365
+            } else {
366
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
367
+                array_unshift($uuidAttributes, 'auto');
368
+                if(!in_array($this->configuration->$effectiveSetting,
369
+                            $uuidAttributes)
370
+                    && (!is_null($this->configID))) {
371
+                    $this->configuration->$effectiveSetting = 'auto';
372
+                    $this->configuration->saveConfiguration();
373
+                    \OCP\Util::writeLog('user_ldap',
374
+                                        'Illegal value for the '.
375
+                                        $effectiveSetting.', '.'reset to '.
376
+                                        'autodetect.', ILogger::INFO);
377
+                }
378
+
379
+            }
380
+        }
381
+
382
+        $backupPort = (int)$this->configuration->ldapBackupPort;
383
+        if ($backupPort <= 0) {
384
+            $this->configuration->backupPort = $this->configuration->ldapPort;
385
+        }
386
+
387
+        //make sure empty search attributes are saved as simple, empty array
388
+        $saKeys = array('ldapAttributesForUserSearch',
389
+                        'ldapAttributesForGroupSearch');
390
+        foreach($saKeys as $key) {
391
+            $val = $this->configuration->$key;
392
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
393
+                $this->configuration->$key = array();
394
+            }
395
+        }
396
+
397
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
398
+            && $this->configuration->ldapTLS) {
399
+            $this->configuration->ldapTLS = false;
400
+            \OCP\Util::writeLog(
401
+                'user_ldap',
402
+                'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
403
+                ILogger::INFO
404
+            );
405
+        }
406
+    }
407
+
408
+    /**
409
+     * @return bool
410
+     */
411
+    private function doCriticalValidation() {
412
+        $configurationOK = true;
413
+        $errorStr = 'Configuration Error (prefix '.
414
+            (string)$this->configPrefix .'): ';
415
+
416
+        //options that shall not be empty
417
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
418
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
419
+        foreach($options as $key) {
420
+            $val = $this->configuration->$key;
421
+            if(empty($val)) {
422
+                switch($key) {
423
+                    case 'ldapHost':
424
+                        $subj = 'LDAP Host';
425
+                        break;
426
+                    case 'ldapPort':
427
+                        $subj = 'LDAP Port';
428
+                        break;
429
+                    case 'ldapUserDisplayName':
430
+                        $subj = 'LDAP User Display Name';
431
+                        break;
432
+                    case 'ldapGroupDisplayName':
433
+                        $subj = 'LDAP Group Display Name';
434
+                        break;
435
+                    case 'ldapLoginFilter':
436
+                        $subj = 'LDAP Login Filter';
437
+                        break;
438
+                    default:
439
+                        $subj = $key;
440
+                        break;
441
+                }
442
+                $configurationOK = false;
443
+                \OCP\Util::writeLog(
444
+                    'user_ldap',
445
+                    $errorStr.'No '.$subj.' given!',
446
+                    ILogger::WARN
447
+                );
448
+            }
449
+        }
450
+
451
+        //combinations
452
+        $agent = $this->configuration->ldapAgentName;
453
+        $pwd = $this->configuration->ldapAgentPassword;
454
+        if (
455
+            ($agent === ''  && $pwd !== '')
456
+            || ($agent !== '' && $pwd === '')
457
+        ) {
458
+            \OCP\Util::writeLog(
459
+                'user_ldap',
460
+                $errorStr.'either no password is given for the user ' .
461
+                    'agent or a password is given, but not an LDAP agent.',
462
+                ILogger::WARN);
463
+            $configurationOK = false;
464
+        }
465
+
466
+        $base = $this->configuration->ldapBase;
467
+        $baseUsers = $this->configuration->ldapBaseUsers;
468
+        $baseGroups = $this->configuration->ldapBaseGroups;
469
+
470
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
471
+            \OCP\Util::writeLog(
472
+                'user_ldap',
473
+                $errorStr.'Not a single Base DN given.',
474
+                ILogger::WARN
475
+            );
476
+            $configurationOK = false;
477
+        }
478
+
479
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
480
+            === false) {
481
+            \OCP\Util::writeLog(
482
+                'user_ldap',
483
+                $errorStr.'login filter does not contain %uid place holder.',
484
+                ILogger::WARN
485
+            );
486
+            $configurationOK = false;
487
+        }
488
+
489
+        return $configurationOK;
490
+    }
491
+
492
+    /**
493
+     * Validates the user specified configuration
494
+     * @return bool true if configuration seems OK, false otherwise
495
+     */
496
+    private function validateConfiguration() {
497
+
498
+        if($this->doNotValidate) {
499
+            //don't do a validation if it is a new configuration with pure
500
+            //default values. Will be allowed on changes via __set or
501
+            //setConfiguration
502
+            return false;
503
+        }
504
+
505
+        // first step: "soft" checks: settings that are not really
506
+        // necessary, but advisable. If left empty, give an info message
507
+        $this->doSoftValidation();
508
+
509
+        //second step: critical checks. If left empty or filled wrong, mark as
510
+        //not configured and give a warning.
511
+        return $this->doCriticalValidation();
512
+    }
513
+
514
+
515
+    /**
516
+     * Connects and Binds to LDAP
517
+     */
518
+    private function establishConnection() {
519
+        if(!$this->configuration->ldapConfigurationActive) {
520
+            return null;
521
+        }
522
+        static $phpLDAPinstalled = true;
523
+        if(!$phpLDAPinstalled) {
524
+            return false;
525
+        }
526
+        if(!$this->ignoreValidation && !$this->configured) {
527
+            \OCP\Util::writeLog(
528
+                'user_ldap',
529
+                'Configuration is invalid, cannot connect',
530
+                ILogger::WARN
531
+            );
532
+            return false;
533
+        }
534
+        if(!$this->ldapConnectionRes) {
535
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
536
+                $phpLDAPinstalled = false;
537
+                \OCP\Util::writeLog(
538
+                    'user_ldap',
539
+                    'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
540
+                    ILogger::ERROR
541
+                );
542
+
543
+                return false;
544
+            }
545
+            if($this->configuration->turnOffCertCheck) {
546
+                if(putenv('LDAPTLS_REQCERT=never')) {
547
+                    \OCP\Util::writeLog('user_ldap',
548
+                        'Turned off SSL certificate validation successfully.',
549
+                        ILogger::DEBUG);
550
+                } else {
551
+                    \OCP\Util::writeLog(
552
+                        'user_ldap',
553
+                        'Could not turn off SSL certificate validation.',
554
+                        ILogger::WARN
555
+                    );
556
+                }
557
+            }
558
+
559
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
560
+                || $this->getFromCache('overrideMainServer'));
561
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
562
+            $bindStatus = false;
563
+            $error = -1;
564
+            try {
565
+                if (!$isOverrideMainServer) {
566
+                    $this->doConnect($this->configuration->ldapHost,
567
+                        $this->configuration->ldapPort);
568
+                    $bindStatus = $this->bind();
569
+                    $error = $this->ldap->isResource($this->ldapConnectionRes) ?
570
+                        $this->ldap->errno($this->ldapConnectionRes) : -1;
571
+                }
572
+                if($bindStatus === true) {
573
+                    return $bindStatus;
574
+                }
575
+            } catch (ServerNotAvailableException $e) {
576
+                if(!$isBackupHost) {
577
+                    throw $e;
578
+                }
579
+            }
580
+
581
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
582
+            if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
583
+                $this->doConnect($this->configuration->ldapBackupHost,
584
+                                    $this->configuration->ldapBackupPort);
585
+                $this->bindResult = [];
586
+                $bindStatus = $this->bind();
587
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
588
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
589
+                if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
590
+                    //when bind to backup server succeeded and failed to main server,
591
+                    //skip contacting him until next cache refresh
592
+                    $this->writeToCache('overrideMainServer', true);
593
+                }
594
+            }
595
+
596
+            return $bindStatus;
597
+        }
598
+        return null;
599
+    }
600
+
601
+    /**
602
+     * @param string $host
603
+     * @param string $port
604
+     * @return bool
605
+     * @throws \OC\ServerNotAvailableException
606
+     */
607
+    private function doConnect($host, $port) {
608
+        if ($host === '') {
609
+            return false;
610
+        }
611
+
612
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
613
+
614
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
615
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
616
+        }
617
+
618
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
619
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
620
+        }
621
+
622
+        if($this->configuration->ldapTLS) {
623
+            if(!$this->ldap->startTls($this->ldapConnectionRes)) {
624
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
625
+            }
626
+        }
627
+
628
+        return true;
629
+    }
630
+
631
+    /**
632
+     * Binds to LDAP
633
+     */
634
+    public function bind() {
635
+        if(!$this->configuration->ldapConfigurationActive) {
636
+            return false;
637
+        }
638
+        $cr = $this->ldapConnectionRes;
639
+        if(!$this->ldap->isResource($cr)) {
640
+            $cr = $this->getConnectionResource();
641
+        }
642
+
643
+        if(
644
+            count($this->bindResult) !== 0
645
+            && $this->bindResult['dn'] === $this->configuration->ldapAgentName
646
+            && \OC::$server->getHasher()->verify(
647
+                $this->configPrefix . $this->configuration->ldapAgentPassword,
648
+                $this->bindResult['hash']
649
+            )
650
+        ) {
651
+            // don't attempt to bind again with the same data as before
652
+            // bind might have been invoked via getConnectionResource(),
653
+            // but we need results specifically for e.g. user login
654
+            return $this->bindResult['result'];
655
+        }
656
+
657
+        $ldapLogin = @$this->ldap->bind($cr,
658
+                                        $this->configuration->ldapAgentName,
659
+                                        $this->configuration->ldapAgentPassword);
660
+
661
+        $this->bindResult = [
662
+            'dn' => $this->configuration->ldapAgentName,
663
+            'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
664
+            'result' => $ldapLogin,
665
+        ];
666
+
667
+        if(!$ldapLogin) {
668
+            $errno = $this->ldap->errno($cr);
669
+
670
+            \OCP\Util::writeLog('user_ldap',
671
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
672
+                ILogger::WARN);
673
+
674
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
675
+            if($errno !== 0x00 && $errno !== 0x31) {
676
+                $this->ldapConnectionRes = null;
677
+            }
678
+
679
+            return false;
680
+        }
681
+        return true;
682
+    }
683 683
 
684 684
 }
Please login to merge, or discard this patch.