1<?php 2/** 3 * @copyright Copyright (c) 2016, ownCloud, Inc. 4 * 5 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 6 * @author Christoph Wurst <christoph@winzerhof-wurst.at> 7 * @author Joas Schilling <coding@schilljs.com> 8 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 9 * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> 10 * @author Morris Jobke <hey@morrisjobke.de> 11 * @author Philipp Staiger <philipp@staiger.it> 12 * @author Roger Szabo <roger.szabo@web.de> 13 * @author Thomas Müller <thomas.mueller@tmit.eu> 14 * @author Victor Dubiniuk <dubiniuk@owncloud.com> 15 * @author Vincent Petry <vincent@nextcloud.com> 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 */ 32namespace OCA\User_LDAP\User; 33 34use OCA\User_LDAP\Access; 35use OCA\User_LDAP\Connection; 36use OCA\User_LDAP\Exceptions\AttributeNotSet; 37use OCA\User_LDAP\FilesystemHelper; 38use OCA\User_LDAP\LogWrapper; 39use OCP\IAvatarManager; 40use OCP\IConfig; 41use OCP\ILogger; 42use OCP\Image; 43use OCP\IUser; 44use OCP\IUserManager; 45use OCP\Notification\IManager as INotificationManager; 46 47/** 48 * User 49 * 50 * represents an LDAP user, gets and holds user-specific information from LDAP 51 */ 52class User { 53 /** 54 * @var Access 55 */ 56 protected $access; 57 /** 58 * @var Connection 59 */ 60 protected $connection; 61 /** 62 * @var IConfig 63 */ 64 protected $config; 65 /** 66 * @var FilesystemHelper 67 */ 68 protected $fs; 69 /** 70 * @var Image 71 */ 72 protected $image; 73 /** 74 * @var LogWrapper 75 */ 76 protected $log; 77 /** 78 * @var IAvatarManager 79 */ 80 protected $avatarManager; 81 /** 82 * @var IUserManager 83 */ 84 protected $userManager; 85 /** 86 * @var INotificationManager 87 */ 88 protected $notificationManager; 89 /** 90 * @var string 91 */ 92 protected $dn; 93 /** 94 * @var string 95 */ 96 protected $uid; 97 /** 98 * @var string[] 99 */ 100 protected $refreshedFeatures = []; 101 /** 102 * @var string 103 */ 104 protected $avatarImage; 105 106 /** 107 * DB config keys for user preferences 108 */ 109 public const USER_PREFKEY_FIRSTLOGIN = 'firstLoginAccomplished'; 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 if ($username === null) { 129 $log->log("uid for '$dn' must not be null!", ILogger::ERROR); 130 throw new \InvalidArgumentException('uid must not be null!'); 131 } elseif ($username === '') { 132 $log->log("uid for '$dn' must not be an empty string", ILogger::ERROR); 133 throw new \InvalidArgumentException('uid must not be an empty string!'); 134 } 135 136 $this->access = $access; 137 $this->connection = $access->getConnection(); 138 $this->config = $config; 139 $this->fs = $fs; 140 $this->dn = $dn; 141 $this->uid = $username; 142 $this->image = $image; 143 $this->log = $log; 144 $this->avatarManager = $avatarManager; 145 $this->userManager = $userManager; 146 $this->notificationManager = $notificationManager; 147 148 \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry'); 149 } 150 151 /** 152 * marks a user as deleted 153 * 154 * @throws \OCP\PreConditionNotMetException 155 */ 156 public function markUser() { 157 $curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0'); 158 if ($curValue === '1') { 159 // the user is already marked, do not write to DB again 160 return; 161 } 162 $this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1'); 163 $this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time()); 164 } 165 166 /** 167 * processes results from LDAP for attributes as returned by getAttributesToRead() 168 * @param array $ldapEntry the user entry as retrieved from LDAP 169 */ 170 public function processAttributes($ldapEntry) { 171 //Quota 172 $attr = strtolower($this->connection->ldapQuotaAttribute); 173 if (isset($ldapEntry[$attr])) { 174 $this->updateQuota($ldapEntry[$attr][0]); 175 } else { 176 if ($this->connection->ldapQuotaDefault !== '') { 177 $this->updateQuota(); 178 } 179 } 180 unset($attr); 181 182 //displayName 183 $displayName = $displayName2 = ''; 184 $attr = strtolower($this->connection->ldapUserDisplayName); 185 if (isset($ldapEntry[$attr])) { 186 $displayName = (string)$ldapEntry[$attr][0]; 187 } 188 $attr = strtolower($this->connection->ldapUserDisplayName2); 189 if (isset($ldapEntry[$attr])) { 190 $displayName2 = (string)$ldapEntry[$attr][0]; 191 } 192 if ($displayName !== '') { 193 $this->composeAndStoreDisplayName($displayName, $displayName2); 194 $this->access->cacheUserDisplayName( 195 $this->getUsername(), 196 $displayName, 197 $displayName2 198 ); 199 } 200 unset($attr); 201 202 //Email 203 //email must be stored after displayname, because it would cause a user 204 //change event that will trigger fetching the display name again 205 $attr = strtolower($this->connection->ldapEmailAttribute); 206 if (isset($ldapEntry[$attr])) { 207 $this->updateEmail($ldapEntry[$attr][0]); 208 } 209 unset($attr); 210 211 // LDAP Username, needed for s2s sharing 212 if (isset($ldapEntry['uid'])) { 213 $this->storeLDAPUserName($ldapEntry['uid'][0]); 214 } elseif (isset($ldapEntry['samaccountname'])) { 215 $this->storeLDAPUserName($ldapEntry['samaccountname'][0]); 216 } 217 218 //homePath 219 if (strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) { 220 $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:'))); 221 if (isset($ldapEntry[$attr])) { 222 $this->access->cacheUserHome( 223 $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0])); 224 } 225 } 226 227 //memberOf groups 228 $cacheKey = 'getMemberOf'.$this->getUsername(); 229 $groups = false; 230 if (isset($ldapEntry['memberof'])) { 231 $groups = $ldapEntry['memberof']; 232 } 233 $this->connection->writeToCache($cacheKey, $groups); 234 235 //external storage var 236 $attr = strtolower($this->connection->ldapExtStorageHomeAttribute); 237 if (isset($ldapEntry[$attr])) { 238 $this->updateExtStorageHome($ldapEntry[$attr][0]); 239 } 240 unset($attr); 241 242 //Avatar 243 /** @var Connection $connection */ 244 $connection = $this->access->getConnection(); 245 $attributes = $connection->resolveRule('avatar'); 246 foreach ($attributes as $attribute) { 247 if (isset($ldapEntry[$attribute])) { 248 $this->avatarImage = $ldapEntry[$attribute][0]; 249 // the call to the method that saves the avatar in the file 250 // system must be postponed after the login. It is to ensure 251 // external mounts are mounted properly (e.g. with login 252 // credentials from the session). 253 \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin'); 254 break; 255 } 256 } 257 } 258 259 /** 260 * @brief returns the LDAP DN of the user 261 * @return string 262 */ 263 public function getDN() { 264 return $this->dn; 265 } 266 267 /** 268 * @brief returns the Nextcloud internal username of the user 269 * @return string 270 */ 271 public function getUsername() { 272 return $this->uid; 273 } 274 275 /** 276 * returns the home directory of the user if specified by LDAP settings 277 * @param string $valueFromLDAP 278 * @return bool|string 279 * @throws \Exception 280 */ 281 public function getHomePath($valueFromLDAP = null) { 282 $path = (string)$valueFromLDAP; 283 $attr = null; 284 285 if (is_null($valueFromLDAP) 286 && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0 287 && $this->access->connection->homeFolderNamingRule !== 'attr:') { 288 $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:')); 289 $homedir = $this->access->readAttribute( 290 $this->access->username2dn($this->getUsername()), $attr); 291 if ($homedir && isset($homedir[0])) { 292 $path = $homedir[0]; 293 } 294 } 295 296 if ($path !== '') { 297 //if attribute's value is an absolute path take this, otherwise append it to data dir 298 //check for / at the beginning or pattern c:\ resp. c:/ 299 if ('/' !== $path[0] 300 && !(3 < strlen($path) && ctype_alpha($path[0]) 301 && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2])) 302 ) { 303 $path = $this->config->getSystemValue('datadirectory', 304 \OC::$SERVERROOT.'/data') . '/' . $path; 305 } 306 //we need it to store it in the DB as well in case a user gets 307 //deleted so we can clean up afterwards 308 $this->config->setUserValue( 309 $this->getUsername(), 'user_ldap', 'homePath', $path 310 ); 311 return $path; 312 } 313 314 if (!is_null($attr) 315 && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true) 316 ) { 317 // a naming rule attribute is defined, but it doesn't exist for that LDAP user 318 throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername()); 319 } 320 321 //false will apply default behaviour as defined and done by OC_User 322 $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', ''); 323 return false; 324 } 325 326 public function getMemberOfGroups() { 327 $cacheKey = 'getMemberOf'.$this->getUsername(); 328 $memberOfGroups = $this->connection->getFromCache($cacheKey); 329 if (!is_null($memberOfGroups)) { 330 return $memberOfGroups; 331 } 332 $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf'); 333 $this->connection->writeToCache($cacheKey, $groupDNs); 334 return $groupDNs; 335 } 336 337 /** 338 * @brief reads the image from LDAP that shall be used as Avatar 339 * @return string data (provided by LDAP) | false 340 */ 341 public function getAvatarImage() { 342 if (!is_null($this->avatarImage)) { 343 return $this->avatarImage; 344 } 345 346 $this->avatarImage = false; 347 /** @var Connection $connection */ 348 $connection = $this->access->getConnection(); 349 $attributes = $connection->resolveRule('avatar'); 350 foreach ($attributes as $attribute) { 351 $result = $this->access->readAttribute($this->dn, $attribute); 352 if ($result !== false && is_array($result) && isset($result[0])) { 353 $this->avatarImage = $result[0]; 354 break; 355 } 356 } 357 358 return $this->avatarImage; 359 } 360 361 /** 362 * @brief marks the user as having logged in at least once 363 * @return null 364 */ 365 public function markLogin() { 366 $this->config->setUserValue( 367 $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1); 368 } 369 370 /** 371 * Stores a key-value pair in relation to this user 372 * 373 * @param string $key 374 * @param string $value 375 */ 376 private function store($key, $value) { 377 $this->config->setUserValue($this->uid, 'user_ldap', $key, $value); 378 } 379 380 /** 381 * Composes the display name and stores it in the database. The final 382 * display name is returned. 383 * 384 * @param string $displayName 385 * @param string $displayName2 386 * @return string the effective display name 387 */ 388 public function composeAndStoreDisplayName($displayName, $displayName2 = '') { 389 $displayName2 = (string)$displayName2; 390 if ($displayName2 !== '') { 391 $displayName .= ' (' . $displayName2 . ')'; 392 } 393 $oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null); 394 if ($oldName !== $displayName) { 395 $this->store('displayName', $displayName); 396 $user = $this->userManager->get($this->getUsername()); 397 if (!empty($oldName) && $user instanceof \OC\User\User) { 398 // if it was empty, it would be a new record, not a change emitting the trigger could 399 // potentially cause a UniqueConstraintViolationException, depending on some factors. 400 $user->triggerChange('displayName', $displayName, $oldName); 401 } 402 } 403 return $displayName; 404 } 405 406 /** 407 * Stores the LDAP Username in the Database 408 * @param string $userName 409 */ 410 public function storeLDAPUserName($userName) { 411 $this->store('uid', $userName); 412 } 413 414 /** 415 * @brief checks whether an update method specified by feature was run 416 * already. If not, it will marked like this, because it is expected that 417 * the method will be run, when false is returned. 418 * @param string $feature email | quota | avatar (can be extended) 419 * @return bool 420 */ 421 private function wasRefreshed($feature) { 422 if (isset($this->refreshedFeatures[$feature])) { 423 return true; 424 } 425 $this->refreshedFeatures[$feature] = 1; 426 return false; 427 } 428 429 /** 430 * fetches the email from LDAP and stores it as Nextcloud user value 431 * @param string $valueFromLDAP if known, to save an LDAP read request 432 * @return null 433 */ 434 public function updateEmail($valueFromLDAP = null) { 435 if ($this->wasRefreshed('email')) { 436 return; 437 } 438 $email = (string)$valueFromLDAP; 439 if (is_null($valueFromLDAP)) { 440 $emailAttribute = $this->connection->ldapEmailAttribute; 441 if ($emailAttribute !== '') { 442 $aEmail = $this->access->readAttribute($this->dn, $emailAttribute); 443 if (is_array($aEmail) && (count($aEmail) > 0)) { 444 $email = (string)$aEmail[0]; 445 } 446 } 447 } 448 if ($email !== '') { 449 $user = $this->userManager->get($this->uid); 450 if (!is_null($user)) { 451 $currentEmail = (string)$user->getSystemEMailAddress(); 452 if ($currentEmail !== $email) { 453 $user->setEMailAddress($email); 454 } 455 } 456 } 457 } 458 459 /** 460 * Overall process goes as follow: 461 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function 462 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota 463 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default') 464 * 4. check if the target user exists and set the quota for the user. 465 * 466 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP 467 * parameter can be passed with the value of the attribute. This value will be considered as the 468 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to 469 * fetch all the user's attributes in one call and use the fetched values in this function. 470 * The expected value for that parameter is a string describing the quota for the user. Valid 471 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in 472 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info) 473 * 474 * fetches the quota from LDAP and stores it as Nextcloud user value 475 * @param string $valueFromLDAP the quota attribute's value can be passed, 476 * to save the readAttribute request 477 * @return null 478 */ 479 public function updateQuota($valueFromLDAP = null) { 480 if ($this->wasRefreshed('quota')) { 481 return; 482 } 483 484 $quotaAttribute = $this->connection->ldapQuotaAttribute; 485 $defaultQuota = $this->connection->ldapQuotaDefault; 486 if ($quotaAttribute === '' && $defaultQuota === '') { 487 return; 488 } 489 490 $quota = false; 491 if (is_null($valueFromLDAP) && $quotaAttribute !== '') { 492 $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute); 493 if ($aQuota && (count($aQuota) > 0) && $this->verifyQuotaValue($aQuota[0])) { 494 $quota = $aQuota[0]; 495 } elseif (is_array($aQuota) && isset($aQuota[0])) { 496 $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::DEBUG); 497 } 498 } elseif ($this->verifyQuotaValue($valueFromLDAP)) { 499 $quota = $valueFromLDAP; 500 } else { 501 $this->log->log('no suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::DEBUG); 502 } 503 504 if ($quota === false && $this->verifyQuotaValue($defaultQuota)) { 505 // quota not found using the LDAP attribute (or not parseable). Try the default quota 506 $quota = $defaultQuota; 507 } elseif ($quota === false) { 508 $this->log->log('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::DEBUG); 509 return; 510 } 511 512 $targetUser = $this->userManager->get($this->uid); 513 if ($targetUser instanceof IUser) { 514 $targetUser->setQuota($quota); 515 } else { 516 $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::INFO); 517 } 518 } 519 520 private function verifyQuotaValue($quotaValue) { 521 return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false; 522 } 523 524 /** 525 * called by a post_login hook to save the avatar picture 526 * 527 * @param array $params 528 */ 529 public function updateAvatarPostLogin($params) { 530 if (isset($params['uid']) && $params['uid'] === $this->getUsername()) { 531 $this->updateAvatar(); 532 } 533 } 534 535 /** 536 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar 537 * @return bool 538 */ 539 public function updateAvatar($force = false) { 540 if (!$force && $this->wasRefreshed('avatar')) { 541 return false; 542 } 543 $avatarImage = $this->getAvatarImage(); 544 if ($avatarImage === false) { 545 //not set, nothing left to do; 546 return false; 547 } 548 549 if (!$this->image->loadFromBase64(base64_encode($avatarImage))) { 550 return false; 551 } 552 553 // use the checksum before modifications 554 $checksum = md5($this->image->data()); 555 556 if ($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '')) { 557 return true; 558 } 559 560 $isSet = $this->setOwnCloudAvatar(); 561 562 if ($isSet) { 563 // save checksum only after successful setting 564 $this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum); 565 } 566 567 return $isSet; 568 } 569 570 /** 571 * @brief sets an image as Nextcloud avatar 572 * @return bool 573 */ 574 private function setOwnCloudAvatar() { 575 if (!$this->image->valid()) { 576 $this->log->log('avatar image data from LDAP invalid for '.$this->dn, ILogger::ERROR); 577 return false; 578 } 579 580 581 //make sure it is a square and not bigger than 128x128 582 $size = min([$this->image->width(), $this->image->height(), 128]); 583 if (!$this->image->centerCrop($size)) { 584 $this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR); 585 return false; 586 } 587 588 if (!$this->fs->isLoaded()) { 589 $this->fs->setup($this->uid); 590 } 591 592 try { 593 $avatar = $this->avatarManager->getAvatar($this->uid); 594 $avatar->set($this->image); 595 return true; 596 } catch (\Exception $e) { 597 \OC::$server->getLogger()->logException($e, [ 598 'message' => 'Could not set avatar for ' . $this->dn, 599 'level' => ILogger::INFO, 600 'app' => 'user_ldap', 601 ]); 602 } 603 return false; 604 } 605 606 /** 607 * @throws AttributeNotSet 608 * @throws \OC\ServerNotAvailableException 609 * @throws \OCP\PreConditionNotMetException 610 */ 611 public function getExtStorageHome():string { 612 $value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', ''); 613 if ($value !== '') { 614 return $value; 615 } 616 617 $value = $this->updateExtStorageHome(); 618 if ($value !== '') { 619 return $value; 620 } 621 622 throw new AttributeNotSet(sprintf( 623 'external home storage attribute yield no value for %s', $this->getUsername() 624 )); 625 } 626 627 /** 628 * @throws \OCP\PreConditionNotMetException 629 * @throws \OC\ServerNotAvailableException 630 */ 631 public function updateExtStorageHome(string $valueFromLDAP = null):string { 632 if ($valueFromLDAP === null) { 633 $extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute); 634 } else { 635 $extHomeValues = [$valueFromLDAP]; 636 } 637 if ($extHomeValues && isset($extHomeValues[0])) { 638 $extHome = $extHomeValues[0]; 639 $this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome); 640 return $extHome; 641 } else { 642 $this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome'); 643 return ''; 644 } 645 } 646 647 /** 648 * called by a post_login hook to handle password expiry 649 * 650 * @param array $params 651 */ 652 public function handlePasswordExpiry($params) { 653 $ppolicyDN = $this->connection->ldapDefaultPPolicyDN; 654 if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) { 655 return;//password expiry handling disabled 656 } 657 $uid = $params['uid']; 658 if (isset($uid) && $uid === $this->getUsername()) { 659 //retrieve relevant user attributes 660 $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']); 661 662 if (array_key_exists('pwdpolicysubentry', $result[0])) { 663 $pwdPolicySubentry = $result[0]['pwdpolicysubentry']; 664 if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) { 665 $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN 666 } 667 } 668 669 $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : []; 670 $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : []; 671 $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : []; 672 673 //retrieve relevant password policy attributes 674 $cacheKey = 'ppolicyAttributes' . $ppolicyDN; 675 $result = $this->connection->getFromCache($cacheKey); 676 if (is_null($result)) { 677 $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']); 678 $this->connection->writeToCache($cacheKey, $result); 679 } 680 681 $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : []; 682 $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : []; 683 $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : []; 684 685 //handle grace login 686 if (!empty($pwdGraceUseTime)) { //was this a grace login? 687 if (!empty($pwdGraceAuthNLimit) 688 && count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available? 689 $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true'); 690 header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( 691 'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid])); 692 } else { //no more grace login available 693 header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( 694 'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid])); 695 } 696 exit(); 697 } 698 //handle pwdReset attribute 699 if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change his password 700 $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true'); 701 header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( 702 'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid])); 703 exit(); 704 } 705 //handle password expiry warning 706 if (!empty($pwdChangedTime)) { 707 if (!empty($pwdMaxAge) 708 && !empty($pwdExpireWarning)) { 709 $pwdMaxAgeInt = (int)$pwdMaxAge[0]; 710 $pwdExpireWarningInt = (int)$pwdExpireWarning[0]; 711 if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) { 712 $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]); 713 $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S')); 714 $currentDateTime = new \DateTime(); 715 $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp(); 716 if ($secondsToExpiry <= $pwdExpireWarningInt) { 717 //remove last password expiry warning if any 718 $notification = $this->notificationManager->createNotification(); 719 $notification->setApp('user_ldap') 720 ->setUser($uid) 721 ->setObject('pwd_exp_warn', $uid) 722 ; 723 $this->notificationManager->markProcessed($notification); 724 //create new password expiry warning 725 $notification = $this->notificationManager->createNotification(); 726 $notification->setApp('user_ldap') 727 ->setUser($uid) 728 ->setDateTime($currentDateTime) 729 ->setObject('pwd_exp_warn', $uid) 730 ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)]) 731 ; 732 $this->notificationManager->notify($notification); 733 } 734 } 735 } 736 } 737 } 738 } 739} 740