Passed
Push — master ( 9bf55c...e105d1 )
by Blizzz
11:12 queued 10s
created

User::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 3
nop 10
dl 0
loc 26
rs 9.6666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Juan Pablo Villafáñez <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Roger Szabo <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Victor Dubiniuk <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\User_LDAP\User;
32
33
use OCA\User_LDAP\Access;
34
use OCA\User_LDAP\Connection;
35
use OCA\User_LDAP\Exceptions\AttributeNotSet;
36
use OCA\User_LDAP\FilesystemHelper;
37
use OCA\User_LDAP\LogWrapper;
38
use OCP\IAvatarManager;
39
use OCP\IConfig;
40
use OCP\ILogger;
41
use OCP\Image;
42
use OCP\IUser;
43
use OCP\IUserManager;
44
use OCP\Notification\IManager as INotificationManager;
45
46
/**
47
 * User
48
 *
49
 * represents an LDAP user, gets and holds user-specific information from LDAP
50
 */
51
class User {
52
	/**
53
	 * @var Access
54
	 */
55
	protected $access;
56
	/**
57
	 * @var Connection
58
	 */
59
	protected $connection;
60
	/**
61
	 * @var IConfig
62
	 */
63
	protected $config;
64
	/**
65
	 * @var FilesystemHelper
66
	 */
67
	protected $fs;
68
	/**
69
	 * @var Image
70
	 */
71
	protected $image;
72
	/**
73
	 * @var LogWrapper
74
	 */
75
	protected $log;
76
	/**
77
	 * @var IAvatarManager
78
	 */
79
	protected $avatarManager;
80
	/**
81
	 * @var IUserManager
82
	 */
83
	protected $userManager;
84
	/**
85
	 * @var INotificationManager
86
	 */
87
	protected $notificationManager;
88
	/**
89
	 * @var string
90
	 */
91
	protected $dn;
92
	/**
93
	 * @var string
94
	 */
95
	protected $uid;
96
	/**
97
	 * @var string[]
98
	 */
99
	protected $refreshedFeatures = array();
100
	/**
101
	 * @var string
102
	 */
103
	protected $avatarImage;
104
105
	/**
106
	 * DB config keys for user preferences
107
	 */
108
	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
109
	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
110
111
	/**
112
	 * @brief constructor, make sure the subclasses call this one!
113
	 * @param string $username the internal username
114
	 * @param string $dn the LDAP DN
115
	 * @param Access $access
116
	 * @param IConfig $config
117
	 * @param FilesystemHelper $fs
118
	 * @param Image $image any empty instance
119
	 * @param LogWrapper $log
120
	 * @param IAvatarManager $avatarManager
121
	 * @param IUserManager $userManager
122
	 * @param INotificationManager $notificationManager
123
	 */
124
	public function __construct($username, $dn, Access $access,
125
		IConfig $config, FilesystemHelper $fs, Image $image,
126
		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
127
		INotificationManager $notificationManager) {
128
	
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
		} else if ($username === '') {
133
			$log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
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
	 * @brief updates properties like email, quota or avatar provided by LDAP
154
	 * @return null
155
	 */
156
	public function update() {
157
		if(is_null($this->dn)) {
0 ignored issues
show
introduced by
The condition is_null($this->dn) is always false.
Loading history...
158
			return null;
159
		}
160
161
		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
162
				self::USER_PREFKEY_FIRSTLOGIN, 0);
163
164
		if($this->needsRefresh()) {
165
			$this->updateEmail();
166
			$this->updateQuota();
167
			if($hasLoggedIn !== 0) {
0 ignored issues
show
introduced by
The condition $hasLoggedIn !== 0 is always true.
Loading history...
168
				//we do not need to try it, when the user has not been logged in
169
				//before, because the file system will not be ready.
170
				$this->updateAvatar();
171
				//in order to get an avatar as soon as possible, mark the user
172
				//as refreshed only when updating the avatar did happen
173
				$this->markRefreshTime();
174
			}
175
		}
176
	}
177
178
	/**
179
	 * processes results from LDAP for attributes as returned by getAttributesToRead()
180
	 * @param array $ldapEntry the user entry as retrieved from LDAP
181
	 */
182
	public function processAttributes($ldapEntry) {
183
		$this->markRefreshTime();
184
		//Quota
185
		$attr = strtolower($this->connection->ldapQuotaAttribute);
186
		if(isset($ldapEntry[$attr])) {
187
			$this->updateQuota($ldapEntry[$attr][0]);
188
		} else {
189
			if ($this->connection->ldapQuotaDefault !== '') {
190
				$this->updateQuota();
191
			}
192
		}
193
		unset($attr);
194
195
		//displayName
196
		$displayName = $displayName2 = '';
197
		$attr = strtolower($this->connection->ldapUserDisplayName);
198
		if(isset($ldapEntry[$attr])) {
199
			$displayName = (string)$ldapEntry[$attr][0];
200
		}
201
		$attr = strtolower($this->connection->ldapUserDisplayName2);
202
		if(isset($ldapEntry[$attr])) {
203
			$displayName2 = (string)$ldapEntry[$attr][0];
204
		}
205
		if ($displayName !== '') {
206
			$this->composeAndStoreDisplayName($displayName, $displayName2);
207
			$this->access->cacheUserDisplayName(
208
				$this->getUsername(),
209
				$displayName,
210
				$displayName2
211
			);
212
		}
213
		unset($attr);
214
215
		//Email
216
		//email must be stored after displayname, because it would cause a user
217
		//change event that will trigger fetching the display name again
218
		$attr = strtolower($this->connection->ldapEmailAttribute);
219
		if(isset($ldapEntry[$attr])) {
220
			$this->updateEmail($ldapEntry[$attr][0]);
221
		}
222
		unset($attr);
223
224
		// LDAP Username, needed for s2s sharing
225
		if(isset($ldapEntry['uid'])) {
226
			$this->storeLDAPUserName($ldapEntry['uid'][0]);
227
		} else if(isset($ldapEntry['samaccountname'])) {
228
			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
229
		}
230
231
		//homePath
232
		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
233
			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
234
			if(isset($ldapEntry[$attr])) {
235
				$this->access->cacheUserHome(
236
					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
237
			}
238
		}
239
240
		//memberOf groups
241
		$cacheKey = 'getMemberOf'.$this->getUsername();
242
		$groups = false;
243
		if(isset($ldapEntry['memberof'])) {
244
			$groups = $ldapEntry['memberof'];
245
		}
246
		$this->connection->writeToCache($cacheKey, $groups);
247
248
		//external storage var
249
		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
250
		if(isset($ldapEntry[$attr])) {
251
			$this->updateExtStorageHome($ldapEntry[$attr][0]);
252
		}
253
		unset($attr);
254
255
		//Avatar
256
		/** @var Connection $connection */
257
		$connection = $this->access->getConnection();
258
		$attributes = $connection->resolveRule('avatar');
259
		foreach ($attributes as $attribute)  {
260
			if(isset($ldapEntry[$attribute])) {
261
				$this->avatarImage = $ldapEntry[$attribute][0];
262
				// the call to the method that saves the avatar in the file
263
				// system must be postponed after the login. It is to ensure
264
				// external mounts are mounted properly (e.g. with login
265
				// credentials from the session).
266
				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
267
				break;
268
			}
269
		}
270
	}
271
272
	/**
273
	 * @brief returns the LDAP DN of the user
274
	 * @return string
275
	 */
276
	public function getDN() {
277
		return $this->dn;
278
	}
279
280
	/**
281
	 * @brief returns the Nextcloud internal username of the user
282
	 * @return string
283
	 */
284
	public function getUsername() {
285
		return $this->uid;
286
	}
287
288
	/**
289
	 * returns the home directory of the user if specified by LDAP settings
290
	 * @param string $valueFromLDAP
291
	 * @return bool|string
292
	 * @throws \Exception
293
	 */
294
	public function getHomePath($valueFromLDAP = null) {
295
		$path = (string)$valueFromLDAP;
296
		$attr = null;
297
298
		if (is_null($valueFromLDAP)
299
		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
300
		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
301
		{
302
			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
303
			$homedir = $this->access->readAttribute(
304
				$this->access->username2dn($this->getUsername()), $attr);
305
			if ($homedir && isset($homedir[0])) {
306
				$path = $homedir[0];
307
			}
308
		}
309
310
		if ($path !== '') {
311
			//if attribute's value is an absolute path take this, otherwise append it to data dir
312
			//check for / at the beginning or pattern c:\ resp. c:/
313
			if(   '/' !== $path[0]
314
			   && !(3 < strlen($path) && ctype_alpha($path[0])
315
			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
316
			) {
317
				$path = $this->config->getSystemValue('datadirectory',
318
						\OC::$SERVERROOT.'/data' ) . '/' . $path;
319
			}
320
			//we need it to store it in the DB as well in case a user gets
321
			//deleted so we can clean up afterwards
322
			$this->config->setUserValue(
323
				$this->getUsername(), 'user_ldap', 'homePath', $path
324
			);
325
			return $path;
326
		}
327
328
		if(    !is_null($attr)
329
			&& $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

329
			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', /** @scrutinizer ignore-type */ true)
Loading history...
330
		) {
331
			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
332
			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
333
		}
334
335
		//false will apply default behaviour as defined and done by OC_User
336
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
337
		return false;
338
	}
339
340
	public function getMemberOfGroups() {
341
		$cacheKey = 'getMemberOf'.$this->getUsername();
342
		$memberOfGroups = $this->connection->getFromCache($cacheKey);
343
		if(!is_null($memberOfGroups)) {
344
			return $memberOfGroups;
345
		}
346
		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
347
		$this->connection->writeToCache($cacheKey, $groupDNs);
348
		return $groupDNs;
349
	}
350
351
	/**
352
	 * @brief reads the image from LDAP that shall be used as Avatar
353
	 * @return string data (provided by LDAP) | false
354
	 */
355
	public function getAvatarImage() {
356
		if(!is_null($this->avatarImage)) {
0 ignored issues
show
introduced by
The condition is_null($this->avatarImage) is always false.
Loading history...
357
			return $this->avatarImage;
358
		}
359
360
		$this->avatarImage = false;
361
		/** @var Connection $connection */
362
		$connection = $this->access->getConnection();
363
		$attributes = $connection->resolveRule('avatar');
364
		foreach($attributes as $attribute) {
365
			$result = $this->access->readAttribute($this->dn, $attribute);
366
			if($result !== false && is_array($result) && isset($result[0])) {
367
				$this->avatarImage = $result[0];
368
				break;
369
			}
370
		}
371
372
		return $this->avatarImage;
373
	}
374
375
	/**
376
	 * @brief marks the user as having logged in at least once
377
	 * @return null
378
	 */
379
	public function markLogin() {
380
		$this->config->setUserValue(
381
			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
382
	}
383
384
	/**
385
	 * @brief marks the time when user features like email have been updated
386
	 * @return null
387
	 */
388
	public function markRefreshTime() {
389
		$this->config->setUserValue(
390
			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
391
	}
392
393
	/**
394
	 * @brief checks whether user features needs to be updated again by
395
	 * comparing the difference of time of the last refresh to now with the
396
	 * desired interval
397
	 * @return bool
398
	 */
399
	private function needsRefresh() {
400
		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
401
			self::USER_PREFKEY_LASTREFRESH, 0);
402
403
		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
404
			return false;
405
		}
406
		return  true;
407
	}
408
409
	/**
410
	 * Stores a key-value pair in relation to this user
411
	 *
412
	 * @param string $key
413
	 * @param string $value
414
	 */
415
	private function store($key, $value) {
416
		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
417
	}
418
419
	/**
420
	 * Composes the display name and stores it in the database. The final
421
	 * display name is returned.
422
	 *
423
	 * @param string $displayName
424
	 * @param string $displayName2
425
	 * @return string the effective display name
426
	 */
427
	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
428
		$displayName2 = (string)$displayName2;
429
		if($displayName2 !== '') {
430
			$displayName .= ' (' . $displayName2 . ')';
431
		}
432
		$oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
433
		if ($oldName !== $displayName)  {
434
			$this->store('displayName', $displayName);
435
			$user = $this->userManager->get($this->getUsername());
436
			if (!empty($oldName) && $user instanceof \OC\User\User) {
437
				// if it was empty, it would be a new record, not a change emitting the trigger could
438
				// potentially cause a UniqueConstraintViolationException, depending on some factors.
439
				$user->triggerChange('displayName', $displayName, $oldName);
440
			}
441
		}
442
		return $displayName;
443
	}
444
445
	/**
446
	 * Stores the LDAP Username in the Database
447
	 * @param string $userName
448
	 */
449
	public function storeLDAPUserName($userName) {
450
		$this->store('uid', $userName);
451
	}
452
453
	/**
454
	 * @brief checks whether an update method specified by feature was run
455
	 * already. If not, it will marked like this, because it is expected that
456
	 * the method will be run, when false is returned.
457
	 * @param string $feature email | quota | avatar (can be extended)
458
	 * @return bool
459
	 */
460
	private function wasRefreshed($feature) {
461
		if(isset($this->refreshedFeatures[$feature])) {
462
			return true;
463
		}
464
		$this->refreshedFeatures[$feature] = 1;
465
		return false;
466
	}
467
468
	/**
469
	 * fetches the email from LDAP and stores it as Nextcloud user value
470
	 * @param string $valueFromLDAP if known, to save an LDAP read request
471
	 * @return null
472
	 */
473
	public function updateEmail($valueFromLDAP = null) {
474
		if($this->wasRefreshed('email')) {
475
			return;
476
		}
477
		$email = (string)$valueFromLDAP;
478
		if(is_null($valueFromLDAP)) {
479
			$emailAttribute = $this->connection->ldapEmailAttribute;
480
			if ($emailAttribute !== '') {
481
				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
482
				if(is_array($aEmail) && (count($aEmail) > 0)) {
483
					$email = (string)$aEmail[0];
484
				}
485
			}
486
		}
487
		if ($email !== '') {
488
			$user = $this->userManager->get($this->uid);
489
			if (!is_null($user)) {
490
				$currentEmail = (string)$user->getEMailAddress();
491
				if ($currentEmail !== $email) {
492
					$user->setEMailAddress($email);
493
				}
494
			}
495
		}
496
	}
497
498
	/**
499
	 * Overall process goes as follow:
500
	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
501
	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
502
	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
503
	 * 4. check if the target user exists and set the quota for the user.
504
	 *
505
	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
506
	 * parameter can be passed with the value of the attribute. This value will be considered as the
507
	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
508
	 * fetch all the user's attributes in one call and use the fetched values in this function.
509
	 * The expected value for that parameter is a string describing the quota for the user. Valid
510
	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
511
	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
512
	 *
513
	 * fetches the quota from LDAP and stores it as Nextcloud user value
514
	 * @param string $valueFromLDAP the quota attribute's value can be passed,
515
	 * to save the readAttribute request
516
	 * @return null
517
	 */
518
	public function updateQuota($valueFromLDAP = null) {
519
		if($this->wasRefreshed('quota')) {
520
			return;
521
		}
522
523
		$quotaAttribute = $this->connection->ldapQuotaAttribute;
524
		$defaultQuota = $this->connection->ldapQuotaDefault;
525
		if($quotaAttribute === '' && $defaultQuota === '') {
526
			return;
527
		}
528
529
		$quota = false;
530
		if(is_null($valueFromLDAP) && $quotaAttribute !== '') {
531
			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
532
			if($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) {
533
				$quota = $aQuota[0];
534
			} else if(is_array($aQuota) && isset($aQuota[0])) {
535
				$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG);
536
			}
537
		} else if ($this->verifyQuotaValue($valueFromLDAP)) {
538
			$quota = $valueFromLDAP;
539
		} else {
540
			$this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG);
541
		}
542
543
		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
544
			// quota not found using the LDAP attribute (or not parseable). Try the default quota
545
			$quota = $defaultQuota;
546
		} else if($quota === false) {
547
			$this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG);
548
			return;
549
		}
550
551
		$targetUser = $this->userManager->get($this->uid);
552
		if ($targetUser instanceof IUser) {
553
			$targetUser->setQuota($quota);
554
		} else {
555
			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO);
556
		}
557
	}
558
559
	private function verifyQuotaValue($quotaValue) {
560
		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
561
	}
562
563
	/**
564
	 * called by a post_login hook to save the avatar picture
565
	 *
566
	 * @param array $params
567
	 */
568
	public function updateAvatarPostLogin($params) {
569
		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
570
			$this->updateAvatar();
571
		}
572
	}
573
574
	/**
575
	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
576
	 * @return bool
577
	 */
578
	public function updateAvatar($force = false) {
579
		if(!$force && $this->wasRefreshed('avatar')) {
580
			return false;
581
		}
582
		$avatarImage = $this->getAvatarImage();
583
		if($avatarImage === false) {
0 ignored issues
show
introduced by
The condition $avatarImage === false is always false.
Loading history...
584
			//not set, nothing left to do;
585
			return false;
586
		}
587
588
		if(!$this->image->loadFromBase64(base64_encode($avatarImage))) {
589
			return false;
590
		}
591
592
		// use the checksum before modifications
593
		$checksum = md5($this->image->data());
594
595
		if($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '')) {
596
			return true;
597
		}
598
599
		$isSet = $this->setOwnCloudAvatar();
600
601
		if($isSet) {
602
			// save checksum only after successful setting
603
			$this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
604
		}
605
606
		return $isSet;
607
	}
608
609
	/**
610
	 * @brief sets an image as Nextcloud avatar
611
	 * @return bool
612
	 */
613
	private function setOwnCloudAvatar() {
614
		if(!$this->image->valid()) {
615
			$this->log->log('avatar image data from LDAP invalid for '.$this->dn, ILogger::ERROR);
616
			return false;
617
		}
618
619
620
		//make sure it is a square and not bigger than 128x128
621
		$size = min([$this->image->width(), $this->image->height(), 128]);
622
		if(!$this->image->centerCrop($size)) {
623
			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
624
			return false;
625
		}
626
627
		if(!$this->fs->isLoaded()) {
628
			$this->fs->setup($this->uid);
629
		}
630
631
		try {
632
			$avatar = $this->avatarManager->getAvatar($this->uid);
633
			$avatar->set($this->image);
634
			return true;
635
		} catch (\Exception $e) {
636
			\OC::$server->getLogger()->logException($e, [
637
				'message' => 'Could not set avatar for ' . $this->dn,
638
				'level' => ILogger::INFO,
639
				'app' => 'user_ldap',
640
			]);
641
		}
642
		return false;
643
	}
644
645
	/**
646
	 * @throws AttributeNotSet
647
	 * @throws \OC\ServerNotAvailableException
648
	 * @throws \OCP\PreConditionNotMetException
649
	 */
650
	public function getExtStorageHome():string {
651
		$value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
652
		if ($value !== '') {
653
			return $value;
654
		}
655
656
		$value = $this->updateExtStorageHome();
657
		if ($value !== '') {
658
			return $value;
659
		}
660
661
		throw new AttributeNotSet(sprintf(
662
			'external home storage attribute yield no value for %s', $this->getUsername()
663
		));
664
	}
665
666
	/**
667
	 * @throws \OCP\PreConditionNotMetException
668
	 * @throws \OC\ServerNotAvailableException
669
	 */
670
	public function updateExtStorageHome(string $valueFromLDAP = null):string {
671
		if($valueFromLDAP === null) {
672
			$extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
673
		} else {
674
			$extHomeValues = [$valueFromLDAP];
675
		}
676
		if ($extHomeValues && isset($extHomeValues[0])) {
677
			$extHome = $extHomeValues[0];
678
			$this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
679
			return $extHome;
680
		} else {
681
			$this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
682
			return '';
683
		}
684
	}
685
686
	/**
687
	 * called by a post_login hook to handle password expiry
688
	 *
689
	 * @param array $params
690
	 */
691
	public function handlePasswordExpiry($params) {
692
		$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...
693
		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
694
			return;//password expiry handling disabled
695
		}
696
		$uid = $params['uid'];
697
		if(isset($uid) && $uid === $this->getUsername()) {
698
			//retrieve relevant user attributes
699
			$result = $this->access->search('objectclass=*', array($this->dn), ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
700
			
701
			if(array_key_exists('pwdpolicysubentry', $result[0])) {
702
				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
703
				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
704
					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
705
				}
706
			}
707
			
708
			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
709
			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
710
			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
711
			
712
			//retrieve relevant password policy attributes
713
			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
714
			$result = $this->connection->getFromCache($cacheKey);
715
			if(is_null($result)) {
716
				$result = $this->access->search('objectclass=*', array($ppolicyDN), ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
717
				$this->connection->writeToCache($cacheKey, $result);
718
			}
719
			
720
			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
721
			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
722
			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
723
			
724
			//handle grace login
725
			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
726
			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
727
				if($pwdGraceAuthNLimit 
728
					&& (count($pwdGraceAuthNLimit) > 0)
729
					&&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
730
					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
731
					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
732
					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
733
				} else { //no more grace login available
734
					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
735
					'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
736
				}
737
				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...
738
			}
739
			//handle pwdReset attribute
740
			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
741
				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
742
				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
743
				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
744
				exit();
745
			}
746
			//handle password expiry warning
747
			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
748
				if($pwdMaxAge && (count($pwdMaxAge) > 0)
749
					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
750
					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
751
					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
752
					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
753
						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
754
						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
755
						$currentDateTime = new \DateTime();
756
						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
757
						if($secondsToExpiry <= $pwdExpireWarningInt) {
758
							//remove last password expiry warning if any
759
							$notification = $this->notificationManager->createNotification();
760
							$notification->setApp('user_ldap')
761
								->setUser($uid)
762
								->setObject('pwd_exp_warn', $uid)
763
							;
764
							$this->notificationManager->markProcessed($notification);
765
							//create new password expiry warning
766
							$notification = $this->notificationManager->createNotification();
767
							$notification->setApp('user_ldap')
768
								->setUser($uid)
769
								->setDateTime($currentDateTime)
770
								->setObject('pwd_exp_warn', $uid) 
771
								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
772
							;
773
							$this->notificationManager->notify($notification);
774
						}
775
					}
776
				}
777
			}
778
		}
779
	}
780
}
781