setOption(self::OPTION_USE_HARD_LOCKOUT, true); } public function setSoftLockout() { $this->setOption(self::OPTION_USE_HARD_LOCKOUT, false); } public function setFailedAttemptsBeforeLockout($attempts = 6) { $this->setOption(self::OPTION_LOCKOUT_FAILED_ATTEMPTS, $attempts); } public function setPeriodOfSoftLockout($period = 'PT30M') { $this->setOption(self::OPTION_SOFT_LOCKOUT_PERIOD, $period); } public function setLockStorage($implementation = RdfLockoutStorage::class) { $this->setOption(self::OPTION_LOCKOUT_STORAGE, $implementation); } public function setNonLockingRoles(array $roles) { $this->setOption(self::OPTION_NON_LOCKING_ROLES, $roles); } /** * Returns proper lockout implementation * @return LockoutStorage|RdfLockoutStorage */ protected function getLockout() { if (!$this->lockout || !$this->lockout instanceof LockoutStorage) { $lockout = $this->getOption(self::OPTION_LOCKOUT_STORAGE); $this->lockout = ($lockout and class_exists($lockout)) ? new $lockout() : new RdfLockoutStorage(); } return $this->lockout; } /** * @param LoginFailedEvent $event * @throws core_kernel_users_Exception */ public function catchFailedLogin(LoginFailedEvent $event) { $this->increaseLoginFails($event->getLogin()); } /** * @param LoginSucceedEvent $event * @throws core_kernel_users_Exception */ public function catchSucceedLogin(LoginSucceedEvent $event) { $this->unlockUser(UserHelper::getUser($this->getLockout()->getUser($event->getLogin()))); } /** * @param string $login * * @throws core_kernel_users_Exception * @throws Exception */ private function increaseLoginFails($login) { $user = UserHelper::getUser($this->getLockout()->getUser($login)); /** @var DateInterval $remaining */ $remaining = $this->getLockoutRemainingTime($login); if ($remaining && !$remaining->invert) { $failures = $this->getLockout()->getFailures($login); } else { $this->unlockUser($user); $failures = 0; } $failures++; if ($failures >= (int)$this->getOption(self::OPTION_LOCKOUT_FAILED_ATTEMPTS)) { $this->lockUser($user); } $this->getLockout()->setFailures($login, $failures); } /** * @param User $user * @return bool|mixed * @throws core_kernel_users_Exception */ public function lockUser(User $user) { $currentUser = tao_models_classes_UserService::singleton()->getCurrentUser(); $blocker = $currentUser ? UserHelper::getUser($currentUser) : $user; if (!$this->isLockable($user)) { return false; } $this->getLockout()->setLockedStatus( UserHelper::getUserLogin($user), $blocker->getIdentifier() ); return true; } /** * @param $user * * @return bool * @throws core_kernel_users_Exception */ public function unlockUser(User $user) { $login = UserHelper::getUserLogin($user); $this->getLockout()->setUnlockedStatus($login); $this->getLockout()->setFailures($login, 0); return true; } /** * @param $login * * @return bool * @throws core_kernel_users_Exception * @throws Exception */ public function isLocked($login) { $status = $this->getLockout()->getStatus($login); if (empty($status)) { return false; } // hard lockout, only admin can reset if ($status && $this->getOption(self::OPTION_USE_HARD_LOCKOUT)) { return true; } $lockedBy = UserHelper::getUser($this->getLockout()->getLockedBy($login)); $user = UserHelper::getUser($this->getLockout()->getUser($login)); if ($lockedBy === null) { return false; } if ($lockedBy->getIdentifier() !== $user->getIdentifier()) { return true; } $lockoutPeriod = new DateInterval($this->getOption(self::OPTION_SOFT_LOCKOUT_PERIOD)); $lastFailureTime = (new DateTimeImmutable())->setTimestamp( (int)(string)$this->getLockout()->getLastFailureTime($login) ); return $lastFailureTime->add($lockoutPeriod) > new DateTimeImmutable(); } /** * @param $user * @return bool */ public function isLockable(User $user) { if ($user->getIdentifier() === LOCAL_NAMESPACE . TaoOntology::DEFAULT_USER_URI_SUFFIX) { return false; } $nonLockingRoles = $this->getOption(self::OPTION_NON_LOCKING_ROLES); if ($nonLockingRoles && is_array($nonLockingRoles) && count($nonLockingRoles)) { return !count(array_intersect($user->getRoles(), $nonLockingRoles)); } return true; } /** * @param $login * @return bool|DateInterval * @throws Exception */ public function getLockoutRemainingTime($login) { $lastFailure = $this->getLockout()->getLastFailureTime($login); if (!$lastFailure) { return false; } $unlockTime = (new DateTime('now')) ->setTimestamp($lastFailure->literal) ->add(new DateInterval($this->getOption(self::OPTION_SOFT_LOCKOUT_PERIOD))); return (new DateTime('now'))->diff($unlockTime); } /** * @param $login * * @return bool|int|mixed * @throws core_kernel_users_Exception * @throws core_kernel_persistence_Exception */ public function getLockoutRemainingAttempts($login) { $user = UserHelper::getUser($this->getLockout()->getUser($login)); if (!$this->isLockable($user)) { return false; } $allowedAttempts = $this->getOption(self::OPTION_LOCKOUT_FAILED_ATTEMPTS); $failedAttempts = $this->getLockout()->getFailures($login); $rest = $allowedAttempts - $failedAttempts; if ($rest < 0) { return false; } return $rest; } /** * @param $login * * @return array * @throws Exception */ public function getStatusDetails($login) { $user = UserHelper::getUser($this->getLockout()->getUser($login)); $isLocked = $this->isLocked($login); if (!$isLocked) { $this->unlockUser($user); return [ 'locked' => false, 'auto' => false, 'status' => __('enabled'), 'remaining' => null, 'lockable' => $this->isLockable($user) ]; } $remaining = $this->getLockoutRemainingTime($login); $lockedBy = UserHelper::getUser($this->getLockout()->getLockedBy($login)); $autoLocked = false; if ($lockedBy->getIdentifier() !== $user->getIdentifier()) { $status = __('locked by %s', UserHelper::getUserLogin($lockedBy)); } else { $autoLocked = true; $status = $this->getOption(self::OPTION_USE_HARD_LOCKOUT) ? __('self-locked') : __('auto unlocked in %s', tao_helpers_Date::displayInterval($remaining)); } return [ 'locked' => $isLocked, 'auto' => $autoLocked, 'status' => $status, 'remaining' => $remaining, 'lockable' => $this->isLockable($user) ]; } }