Passed
Push — master ( e4dff7...06a1cf )
by Roeland
13:23 queued 01:40
created

User::composeAndStoreDisplayName()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 6
nop 2
dl 0
loc 16
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Juan Pablo Villafáñez <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Philipp Staiger <[email protected]>
12
 * @author Roger Szabo <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Victor Dubiniuk <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OCA\User_LDAP\User;
34
35
use OCA\User_LDAP\Access;
36
use OCA\User_LDAP\Connection;
37
use OCA\User_LDAP\Exceptions\AttributeNotSet;
38
use OCA\User_LDAP\FilesystemHelper;
39
use OCA\User_LDAP\LogWrapper;
40
use OCP\IAvatarManager;
41
use OCP\IConfig;
42
use OCP\ILogger;
43
use OCP\Image;
44
use OCP\IUser;
45
use OCP\IUserManager;
46
use OCP\Notification\IManager as INotificationManager;
47
48
/**
49
 * User
50
 *
51
 * represents an LDAP user, gets and holds user-specific information from LDAP
52
 */
53
class User {
54
	/**
55
	 * @var Access
56
	 */
57
	protected $access;
58
	/**
59
	 * @var Connection
60
	 */
61
	protected $connection;
62
	/**
63
	 * @var IConfig
64
	 */
65
	protected $config;
66
	/**
67
	 * @var FilesystemHelper
68
	 */
69
	protected $fs;
70
	/**
71
	 * @var Image
72
	 */
73
	protected $image;
74
	/**
75
	 * @var LogWrapper
76
	 */
77
	protected $log;
78
	/**
79
	 * @var IAvatarManager
80
	 */
81
	protected $avatarManager;
82
	/**
83
	 * @var IUserManager
84
	 */
85
	protected $userManager;
86
	/**
87
	 * @var INotificationManager
88
	 */
89
	protected $notificationManager;
90
	/**
91
	 * @var string
92
	 */
93
	protected $dn;
94
	/**
95
	 * @var string
96
	 */
97
	protected $uid;
98
	/**
99
	 * @var string[]
100
	 */
101
	protected $refreshedFeatures = [];
102
	/**
103
	 * @var string
104
	 */
105
	protected $avatarImage;
106
107
	/**
108
	 * DB config keys for user preferences
109
	 */
110
	public const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
111
112
	/**
113
	 * @brief constructor, make sure the subclasses call this one!
114
	 * @param string $username the internal username
115
	 * @param string $dn the LDAP DN
116
	 * @param Access $access
117
	 * @param IConfig $config
118
	 * @param FilesystemHelper $fs
119
	 * @param Image $image any empty instance
120
	 * @param LogWrapper $log
121
	 * @param IAvatarManager $avatarManager
122
	 * @param IUserManager $userManager
123
	 * @param INotificationManager $notificationManager
124
	 */
125
	public function __construct($username, $dn, Access $access,
126
		IConfig $config, FilesystemHelper $fs, Image $image,
127
		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
128
		INotificationManager $notificationManager) {
129
		if ($username === null) {
0 ignored issues
show
introduced by
The condition $username === null is always false.
Loading history...
130
			$log->log("uid for '$dn' must not be null!", ILogger::ERROR);
131
			throw new \InvalidArgumentException('uid must not be null!');
132
		} elseif ($username === '') {
133
			$log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

133
			$log->log("uid for '$dn' must not be an empty string", /** @scrutinizer ignore-deprecated */ ILogger::ERROR);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
134
			throw new \InvalidArgumentException('uid must not be an empty string!');
135
		}
136
137
		$this->access              = $access;
138
		$this->connection          = $access->getConnection();
139
		$this->config              = $config;
140
		$this->fs                  = $fs;
141
		$this->dn                  = $dn;
142
		$this->uid                 = $username;
143
		$this->image               = $image;
144
		$this->log                 = $log;
145
		$this->avatarManager       = $avatarManager;
146
		$this->userManager         = $userManager;
147
		$this->notificationManager = $notificationManager;
148
149
		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
150
	}
151
152
	/**
153
	 * marks a user as deleted
154
	 *
155
	 * @throws \OCP\PreConditionNotMetException
156
	 */
157
	public function markUser() {
158
		$curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
159
		if ($curValue === '1') {
160
			// the user is already marked, do not write to DB again
161
			return;
162
		}
163
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
164
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
165
	}
166
167
	/**
168
	 * processes results from LDAP for attributes as returned by getAttributesToRead()
169
	 * @param array $ldapEntry the user entry as retrieved from LDAP
170
	 */
171
	public function processAttributes($ldapEntry) {
172
		//Quota
173
		$attr = strtolower($this->connection->ldapQuotaAttribute);
174
		if (isset($ldapEntry[$attr])) {
175
			$this->updateQuota($ldapEntry[$attr][0]);
176
		} else {
177
			if ($this->connection->ldapQuotaDefault !== '') {
178
				$this->updateQuota();
179
			}
180
		}
181
		unset($attr);
182
183
		//displayName
184
		$displayName = $displayName2 = '';
185
		$attr = strtolower($this->connection->ldapUserDisplayName);
186
		if (isset($ldapEntry[$attr])) {
187
			$displayName = (string)$ldapEntry[$attr][0];
188
		}
189
		$attr = strtolower($this->connection->ldapUserDisplayName2);
190
		if (isset($ldapEntry[$attr])) {
191
			$displayName2 = (string)$ldapEntry[$attr][0];
192
		}
193
		if ($displayName !== '') {
194
			$this->composeAndStoreDisplayName($displayName, $displayName2);
195
			$this->access->cacheUserDisplayName(
196
				$this->getUsername(),
197
				$displayName,
198
				$displayName2
199
			);
200
		}
201
		unset($attr);
202
203
		//Email
204
		//email must be stored after displayname, because it would cause a user
205
		//change event that will trigger fetching the display name again
206
		$attr = strtolower($this->connection->ldapEmailAttribute);
207
		if (isset($ldapEntry[$attr])) {
208
			$this->updateEmail($ldapEntry[$attr][0]);
209
		}
210
		unset($attr);
211
212
		// LDAP Username, needed for s2s sharing
213
		if (isset($ldapEntry['uid'])) {
214
			$this->storeLDAPUserName($ldapEntry['uid'][0]);
215
		} elseif (isset($ldapEntry['samaccountname'])) {
216
			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
217
		}
218
219
		//homePath
220
		if (strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
221
			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
222
			if (isset($ldapEntry[$attr])) {
223
				$this->access->cacheUserHome(
224
					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
225
			}
226
		}
227
228
		//memberOf groups
229
		$cacheKey = 'getMemberOf'.$this->getUsername();
230
		$groups = false;
231
		if (isset($ldapEntry['memberof'])) {
232
			$groups = $ldapEntry['memberof'];
233
		}
234
		$this->connection->writeToCache($cacheKey, $groups);
235
236
		//external storage var
237
		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
238
		if (isset($ldapEntry[$attr])) {
239
			$this->updateExtStorageHome($ldapEntry[$attr][0]);
240
		}
241
		unset($attr);
242
243
		//Avatar
244
		/** @var Connection $connection */
245
		$connection = $this->access->getConnection();
246
		$attributes = $connection->resolveRule('avatar');
247
		foreach ($attributes as $attribute) {
248
			if (isset($ldapEntry[$attribute])) {
249
				$this->avatarImage = $ldapEntry[$attribute][0];
250
				// the call to the method that saves the avatar in the file
251
				// system must be postponed after the login. It is to ensure
252
				// external mounts are mounted properly (e.g. with login
253
				// credentials from the session).
254
				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
255
				break;
256
			}
257
		}
258
	}
259
260
	/**
261
	 * @brief returns the LDAP DN of the user
262
	 * @return string
263
	 */
264
	public function getDN() {
265
		return $this->dn;
266
	}
267
268
	/**
269
	 * @brief returns the Nextcloud internal username of the user
270
	 * @return string
271
	 */
272
	public function getUsername() {
273
		return $this->uid;
274
	}
275
276
	/**
277
	 * returns the home directory of the user if specified by LDAP settings
278
	 * @param string $valueFromLDAP
279
	 * @return bool|string
280
	 * @throws \Exception
281
	 */
282
	public function getHomePath($valueFromLDAP = null) {
283
		$path = (string)$valueFromLDAP;
284
		$attr = null;
285
286
		if (is_null($valueFromLDAP)
287
		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
288
		   && $this->access->connection->homeFolderNamingRule !== 'attr:') {
289
			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
290
			$homedir = $this->access->readAttribute(
291
				$this->access->username2dn($this->getUsername()), $attr);
292
			if ($homedir && isset($homedir[0])) {
293
				$path = $homedir[0];
294
			}
295
		}
296
297
		if ($path !== '') {
298
			//if attribute's value is an absolute path take this, otherwise append it to data dir
299
			//check for / at the beginning or pattern c:\ resp. c:/
300
			if ('/' !== $path[0]
301
			   && !(3 < strlen($path) && ctype_alpha($path[0])
302
				   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
303
			) {
304
				$path = $this->config->getSystemValue('datadirectory',
305
						\OC::$SERVERROOT.'/data') . '/' . $path;
306
			}
307
			//we need it to store it in the DB as well in case a user gets
308
			//deleted so we can clean up afterwards
309
			$this->config->setUserValue(
310
				$this->getUsername(), 'user_ldap', 'homePath', $path
311
			);
312
			return $path;
313
		}
314
315
		if (!is_null($attr)
316
			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $default of OCP\IConfig::getAppValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

316
			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', /** @scrutinizer ignore-type */ true)
Loading history...
317
		) {
318
			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
319
			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
320
		}
321
322
		//false will apply default behaviour as defined and done by OC_User
323
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
324
		return false;
325
	}
326
327
	public function getMemberOfGroups() {
328
		$cacheKey = 'getMemberOf'.$this->getUsername();
329
		$memberOfGroups = $this->connection->getFromCache($cacheKey);
330
		if (!is_null($memberOfGroups)) {
331
			return $memberOfGroups;
332
		}
333
		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
334
		$this->connection->writeToCache($cacheKey, $groupDNs);
335
		return $groupDNs;
336
	}
337
338
	/**
339
	 * @brief reads the image from LDAP that shall be used as Avatar
340
	 * @return string data (provided by LDAP) | false
341
	 */
342
	public function getAvatarImage() {
343
		if (!is_null($this->avatarImage)) {
0 ignored issues
show
introduced by
The condition is_null($this->avatarImage) is always false.
Loading history...
344
			return $this->avatarImage;
345
		}
346
347
		$this->avatarImage = false;
348
		/** @var Connection $connection */
349
		$connection = $this->access->getConnection();
350
		$attributes = $connection->resolveRule('avatar');
351
		foreach ($attributes as $attribute) {
352
			$result = $this->access->readAttribute($this->dn, $attribute);
353
			if ($result !== false && is_array($result) && isset($result[0])) {
354
				$this->avatarImage = $result[0];
355
				break;
356
			}
357
		}
358
359
		return $this->avatarImage;
360
	}
361
362
	/**
363
	 * @brief marks the user as having logged in at least once
364
	 * @return null
365
	 */
366
	public function markLogin() {
367
		$this->config->setUserValue(
368
			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
369
	}
370
371
	/**
372
	 * Stores a key-value pair in relation to this user
373
	 *
374
	 * @param string $key
375
	 * @param string $value
376
	 */
377
	private function store($key, $value) {
378
		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
379
	}
380
381
	/**
382
	 * Composes the display name and stores it in the database. The final
383
	 * display name is returned.
384
	 *
385
	 * @param string $displayName
386
	 * @param string $displayName2
387
	 * @return string the effective display name
388
	 */
389
	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
390
		$displayName2 = (string)$displayName2;
391
		if ($displayName2 !== '') {
392
			$displayName .= ' (' . $displayName2 . ')';
393
		}
394
		$oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
395
		if ($oldName !== $displayName) {
396
			$this->store('displayName', $displayName);
397
			$user = $this->userManager->get($this->getUsername());
398
			if (!empty($oldName) && $user instanceof \OC\User\User) {
399
				// if it was empty, it would be a new record, not a change emitting the trigger could
400
				// potentially cause a UniqueConstraintViolationException, depending on some factors.
401
				$user->triggerChange('displayName', $displayName, $oldName);
402
			}
403
		}
404
		return $displayName;
405
	}
406
407
	/**
408
	 * Stores the LDAP Username in the Database
409
	 * @param string $userName
410
	 */
411
	public function storeLDAPUserName($userName) {
412
		$this->store('uid', $userName);
413
	}
414
415
	/**
416
	 * @brief checks whether an update method specified by feature was run
417
	 * already. If not, it will marked like this, because it is expected that
418
	 * the method will be run, when false is returned.
419
	 * @param string $feature email | quota | avatar (can be extended)
420
	 * @return bool
421
	 */
422
	private function wasRefreshed($feature) {
423
		if (isset($this->refreshedFeatures[$feature])) {
424
			return true;
425
		}
426
		$this->refreshedFeatures[$feature] = 1;
427
		return false;
428
	}
429
430
	/**
431
	 * fetches the email from LDAP and stores it as Nextcloud user value
432
	 * @param string $valueFromLDAP if known, to save an LDAP read request
433
	 * @return null
434
	 */
435
	public function updateEmail($valueFromLDAP = null) {
436
		if ($this->wasRefreshed('email')) {
437
			return;
438
		}
439
		$email = (string)$valueFromLDAP;
440
		if (is_null($valueFromLDAP)) {
441
			$emailAttribute = $this->connection->ldapEmailAttribute;
442
			if ($emailAttribute !== '') {
443
				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
444
				if (is_array($aEmail) && (count($aEmail) > 0)) {
445
					$email = (string)$aEmail[0];
446
				}
447
			}
448
		}
449
		if ($email !== '') {
450
			$user = $this->userManager->get($this->uid);
451
			if (!is_null($user)) {
452
				$currentEmail = (string)$user->getEMailAddress();
453
				if ($currentEmail !== $email) {
454
					$user->setEMailAddress($email);
455
				}
456
			}
457
		}
458
	}
459
460
	/**
461
	 * Overall process goes as follow:
462
	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
463
	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
464
	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
465
	 * 4. check if the target user exists and set the quota for the user.
466
	 *
467
	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
468
	 * parameter can be passed with the value of the attribute. This value will be considered as the
469
	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
470
	 * fetch all the user's attributes in one call and use the fetched values in this function.
471
	 * The expected value for that parameter is a string describing the quota for the user. Valid
472
	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
473
	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
474
	 *
475
	 * fetches the quota from LDAP and stores it as Nextcloud user value
476
	 * @param string $valueFromLDAP the quota attribute's value can be passed,
477
	 * to save the readAttribute request
478
	 * @return null
479
	 */
480
	public function updateQuota($valueFromLDAP = null) {
481
		if ($this->wasRefreshed('quota')) {
482
			return;
483
		}
484
485
		$quotaAttribute = $this->connection->ldapQuotaAttribute;
486
		$defaultQuota = $this->connection->ldapQuotaDefault;
487
		if ($quotaAttribute === '' && $defaultQuota === '') {
488
			return;
489
		}
490
491
		$quota = false;
492
		if (is_null($valueFromLDAP) && $quotaAttribute !== '') {
493
			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
494
			if ($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
495
				$quota = $aQuota[0];
496
			} elseif (is_array($aQuota) && isset($aQuota[0])) {
497
				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

497
				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', /** @scrutinizer ignore-deprecated */ ILogger::DEBUG);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
498
			}
499
		} elseif ($this->verifyQuotaValue($valueFromLDAP)) {
500
			$quota = $valueFromLDAP;
501
		} else {
502
			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

502
			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', /** @scrutinizer ignore-deprecated */ ILogger::DEBUG);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
503
		}
504
505
		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
506
			// quota not found using the LDAP attribute (or not parseable). Try the default quota
507
			$quota = $defaultQuota;
508
		} elseif ($quota === false) {
509
			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

509
			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', /** @scrutinizer ignore-deprecated */ ILogger::DEBUG);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
510
			return;
511
		}
512
513
		$targetUser = $this->userManager->get($this->uid);
514
		if ($targetUser instanceof IUser) {
515
			$targetUser->setQuota($quota);
516
		} else {
517
			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

517
			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', /** @scrutinizer ignore-deprecated */ ILogger::INFO);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
518
		}
519
	}
520
521
	private function verifyQuotaValue($quotaValue) {
522
		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
523
	}
524
525
	/**
526
	 * called by a post_login hook to save the avatar picture
527
	 *
528
	 * @param array $params
529
	 */
530
	public function updateAvatarPostLogin($params) {
531
		if (isset($params['uid']) && $params['uid'] === $this->getUsername()) {
532
			$this->updateAvatar();
533
		}
534
	}
535
536
	/**
537
	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
538
	 * @return bool
539
	 */
540
	public function updateAvatar($force = false) {
541
		if (!$force && $this->wasRefreshed('avatar')) {
542
			return false;
543
		}
544
		$avatarImage = $this->getAvatarImage();
545
		if ($avatarImage === false) {
0 ignored issues
show
introduced by
The condition $avatarImage === false is always false.
Loading history...
546
			//not set, nothing left to do;
547
			return false;
548
		}
549
550
		if (!$this->image->loadFromBase64(base64_encode($avatarImage))) {
551
			return false;
552
		}
553
554
		// use the checksum before modifications
555
		$checksum = md5($this->image->data());
556
557
		if ($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '')) {
558
			return true;
559
		}
560
561
		$isSet = $this->setOwnCloudAvatar();
562
563
		if ($isSet) {
564
			// save checksum only after successful setting
565
			$this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
566
		}
567
568
		return $isSet;
569
	}
570
571
	/**
572
	 * @brief sets an image as Nextcloud avatar
573
	 * @return bool
574
	 */
575
	private function setOwnCloudAvatar() {
576
		if (!$this->image->valid()) {
577
			$this->log->log('avatar image data from LDAP invalid for '.$this->dn, ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

577
			$this->log->log('avatar image data from LDAP invalid for '.$this->dn, /** @scrutinizer ignore-deprecated */ ILogger::ERROR);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
578
			return false;
579
		}
580
581
582
		//make sure it is a square and not bigger than 128x128
583
		$size = min([$this->image->width(), $this->image->height(), 128]);
584
		if (!$this->image->centerCrop($size)) {
585
			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

585
			$this->log->log('croping image for avatar failed for '.$this->dn, /** @scrutinizer ignore-deprecated */ ILogger::ERROR);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
586
			return false;
587
		}
588
589
		if (!$this->fs->isLoaded()) {
590
			$this->fs->setup($this->uid);
591
		}
592
593
		try {
594
			$avatar = $this->avatarManager->getAvatar($this->uid);
595
			$avatar->set($this->image);
596
			return true;
597
		} catch (\Exception $e) {
598
			\OC::$server->getLogger()->logException($e, [
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

598
			/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->logException($e, [
Loading history...
599
				'message' => 'Could not set avatar for ' . $this->dn,
600
				'level' => ILogger::INFO,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

600
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
601
				'app' => 'user_ldap',
602
			]);
603
		}
604
		return false;
605
	}
606
607
	/**
608
	 * @throws AttributeNotSet
609
	 * @throws \OC\ServerNotAvailableException
610
	 * @throws \OCP\PreConditionNotMetException
611
	 */
612
	public function getExtStorageHome():string {
613
		$value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
614
		if ($value !== '') {
615
			return $value;
616
		}
617
618
		$value = $this->updateExtStorageHome();
619
		if ($value !== '') {
620
			return $value;
621
		}
622
623
		throw new AttributeNotSet(sprintf(
624
			'external home storage attribute yield no value for %s', $this->getUsername()
625
		));
626
	}
627
628
	/**
629
	 * @throws \OCP\PreConditionNotMetException
630
	 * @throws \OC\ServerNotAvailableException
631
	 */
632
	public function updateExtStorageHome(string $valueFromLDAP = null):string {
633
		if ($valueFromLDAP === null) {
634
			$extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
635
		} else {
636
			$extHomeValues = [$valueFromLDAP];
637
		}
638
		if ($extHomeValues && isset($extHomeValues[0])) {
639
			$extHome = $extHomeValues[0];
640
			$this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
641
			return $extHome;
642
		} else {
643
			$this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
644
			return '';
645
		}
646
	}
647
648
	/**
649
	 * called by a post_login hook to handle password expiry
650
	 *
651
	 * @param array $params
652
	 */
653
	public function handlePasswordExpiry($params) {
654
		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapDefaultPPolicyDN does not exist on OCA\User_LDAP\Connection. Since you implemented __get, consider adding a @property annotation.
Loading history...
655
		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
656
			return;//password expiry handling disabled
657
		}
658
		$uid = $params['uid'];
659
		if (isset($uid) && $uid === $this->getUsername()) {
660
			//retrieve relevant user attributes
661
			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
662
663
			if (array_key_exists('pwdpolicysubentry', $result[0])) {
664
				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
665
				if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
666
					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
667
				}
668
			}
669
670
			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
671
			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
672
			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
673
674
			//retrieve relevant password policy attributes
675
			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
676
			$result = $this->connection->getFromCache($cacheKey);
677
			if (is_null($result)) {
678
				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
679
				$this->connection->writeToCache($cacheKey, $result);
680
			}
681
682
			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
683
			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
684
			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
685
686
			//handle grace login
687
			if (!empty($pwdGraceUseTime)) { //was this a grace login?
688
				if (!empty($pwdGraceAuthNLimit)
689
					&& count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available?
690
					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
691
					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

691
					header('Location: './** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator()->linkToRouteAbsolute(
Loading history...
692
					'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
693
				} else { //no more grace login available
694
					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

694
					header('Location: './** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator()->linkToRouteAbsolute(
Loading history...
695
					'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid]));
696
				}
697
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
698
			}
699
			//handle pwdReset attribute
700
			if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change his password
701
				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
702
				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

702
				header('Location: './** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator()->linkToRouteAbsolute(
Loading history...
703
				'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
704
				exit();
705
			}
706
			//handle password expiry warning
707
			if (!empty($pwdChangedTime)) {
708
				if (!empty($pwdMaxAge)
709
					&& !empty($pwdExpireWarning)) {
710
					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
711
					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
712
					if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) {
713
						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
714
						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
715
						$currentDateTime = new \DateTime();
716
						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
717
						if ($secondsToExpiry <= $pwdExpireWarningInt) {
718
							//remove last password expiry warning if any
719
							$notification = $this->notificationManager->createNotification();
720
							$notification->setApp('user_ldap')
721
								->setUser($uid)
722
								->setObject('pwd_exp_warn', $uid)
723
							;
724
							$this->notificationManager->markProcessed($notification);
725
							//create new password expiry warning
726
							$notification = $this->notificationManager->createNotification();
727
							$notification->setApp('user_ldap')
728
								->setUser($uid)
729
								->setDateTime($currentDateTime)
730
								->setObject('pwd_exp_warn', $uid)
731
								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
732
							;
733
							$this->notificationManager->notify($notification);
734
						}
735
					}
736
				}
737
			}
738
		}
739
	}
740
}
741