Passed
Push — master ( 092a1f...510a29 )
by Joas
12:31 queued 11s
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 Jörn Friedrich Dreyer <[email protected]>
8
 * @author Juan Pablo Villafáñez <[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
	 * marks a user as deleted
180
	 *
181
	 * @throws \OCP\PreConditionNotMetException
182
	 */
183
	public function markUser() {
184
		$curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
185
		if($curValue === '1') {
186
			// the user is already marked, do not write to DB again
187
			return;
188
		}
189
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
190
		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
191
	}
192
193
	/**
194
	 * processes results from LDAP for attributes as returned by getAttributesToRead()
195
	 * @param array $ldapEntry the user entry as retrieved from LDAP
196
	 */
197
	public function processAttributes($ldapEntry) {
198
		$this->markRefreshTime();
199
		//Quota
200
		$attr = strtolower($this->connection->ldapQuotaAttribute);
201
		if(isset($ldapEntry[$attr])) {
202
			$this->updateQuota($ldapEntry[$attr][0]);
203
		} else {
204
			if ($this->connection->ldapQuotaDefault !== '') {
205
				$this->updateQuota();
206
			}
207
		}
208
		unset($attr);
209
210
		//displayName
211
		$displayName = $displayName2 = '';
212
		$attr = strtolower($this->connection->ldapUserDisplayName);
213
		if(isset($ldapEntry[$attr])) {
214
			$displayName = (string)$ldapEntry[$attr][0];
215
		}
216
		$attr = strtolower($this->connection->ldapUserDisplayName2);
217
		if(isset($ldapEntry[$attr])) {
218
			$displayName2 = (string)$ldapEntry[$attr][0];
219
		}
220
		if ($displayName !== '') {
221
			$this->composeAndStoreDisplayName($displayName, $displayName2);
222
			$this->access->cacheUserDisplayName(
223
				$this->getUsername(),
224
				$displayName,
225
				$displayName2
226
			);
227
		}
228
		unset($attr);
229
230
		//Email
231
		//email must be stored after displayname, because it would cause a user
232
		//change event that will trigger fetching the display name again
233
		$attr = strtolower($this->connection->ldapEmailAttribute);
234
		if(isset($ldapEntry[$attr])) {
235
			$this->updateEmail($ldapEntry[$attr][0]);
236
		}
237
		unset($attr);
238
239
		// LDAP Username, needed for s2s sharing
240
		if(isset($ldapEntry['uid'])) {
241
			$this->storeLDAPUserName($ldapEntry['uid'][0]);
242
		} else if(isset($ldapEntry['samaccountname'])) {
243
			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
244
		}
245
246
		//homePath
247
		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
248
			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
249
			if(isset($ldapEntry[$attr])) {
250
				$this->access->cacheUserHome(
251
					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
252
			}
253
		}
254
255
		//memberOf groups
256
		$cacheKey = 'getMemberOf'.$this->getUsername();
257
		$groups = false;
258
		if(isset($ldapEntry['memberof'])) {
259
			$groups = $ldapEntry['memberof'];
260
		}
261
		$this->connection->writeToCache($cacheKey, $groups);
262
263
		//external storage var
264
		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
265
		if(isset($ldapEntry[$attr])) {
266
			$this->updateExtStorageHome($ldapEntry[$attr][0]);
267
		}
268
		unset($attr);
269
270
		//Avatar
271
		/** @var Connection $connection */
272
		$connection = $this->access->getConnection();
273
		$attributes = $connection->resolveRule('avatar');
274
		foreach ($attributes as $attribute)  {
275
			if(isset($ldapEntry[$attribute])) {
276
				$this->avatarImage = $ldapEntry[$attribute][0];
277
				// the call to the method that saves the avatar in the file
278
				// system must be postponed after the login. It is to ensure
279
				// external mounts are mounted properly (e.g. with login
280
				// credentials from the session).
281
				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
282
				break;
283
			}
284
		}
285
	}
286
287
	/**
288
	 * @brief returns the LDAP DN of the user
289
	 * @return string
290
	 */
291
	public function getDN() {
292
		return $this->dn;
293
	}
294
295
	/**
296
	 * @brief returns the Nextcloud internal username of the user
297
	 * @return string
298
	 */
299
	public function getUsername() {
300
		return $this->uid;
301
	}
302
303
	/**
304
	 * returns the home directory of the user if specified by LDAP settings
305
	 * @param string $valueFromLDAP
306
	 * @return bool|string
307
	 * @throws \Exception
308
	 */
309
	public function getHomePath($valueFromLDAP = null) {
310
		$path = (string)$valueFromLDAP;
311
		$attr = null;
312
313
		if (is_null($valueFromLDAP)
314
		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
315
		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
316
		{
317
			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
318
			$homedir = $this->access->readAttribute(
319
				$this->access->username2dn($this->getUsername()), $attr);
320
			if ($homedir && isset($homedir[0])) {
321
				$path = $homedir[0];
322
			}
323
		}
324
325
		if ($path !== '') {
326
			//if attribute's value is an absolute path take this, otherwise append it to data dir
327
			//check for / at the beginning or pattern c:\ resp. c:/
328
			if(   '/' !== $path[0]
329
			   && !(3 < strlen($path) && ctype_alpha($path[0])
330
			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
331
			) {
332
				$path = $this->config->getSystemValue('datadirectory',
333
						\OC::$SERVERROOT.'/data' ) . '/' . $path;
334
			}
335
			//we need it to store it in the DB as well in case a user gets
336
			//deleted so we can clean up afterwards
337
			$this->config->setUserValue(
338
				$this->getUsername(), 'user_ldap', 'homePath', $path
339
			);
340
			return $path;
341
		}
342
343
		if(    !is_null($attr)
344
			&& $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

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