AuthObserver.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\User\Observer\Backend;
  7. use Magento\Backend\Model\Auth\Session;
  8. use Magento\Backend\Model\UrlInterface;
  9. use Magento\Framework\Encryption\EncryptorInterface;
  10. use Magento\Framework\Event\Observer as EventObserver;
  11. use Magento\Framework\Exception\State\UserLockedException;
  12. use Magento\Framework\Message\ManagerInterface;
  13. use Magento\User\Model\Backend\Config\ObserverConfig;
  14. use Magento\User\Model\ResourceModel\User as ResourceUser;
  15. use Magento\User\Model\User;
  16. use Magento\Framework\Event\ObserverInterface;
  17. use Magento\User\Model\UserFactory;
  18. /**
  19. * User backend observer model for authentication
  20. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  21. */
  22. class AuthObserver implements ObserverInterface
  23. {
  24. /**
  25. * Backend configuration interface
  26. *
  27. * @var ObserverConfig
  28. */
  29. protected $observerConfig;
  30. /**
  31. * Admin user resource model
  32. *
  33. * @var ResourceUser
  34. */
  35. protected $userResource;
  36. /**
  37. * Backend url interface
  38. *
  39. * @var UrlInterface
  40. */
  41. protected $url;
  42. /**
  43. * Backend authorization session
  44. *
  45. * @var Session
  46. */
  47. protected $authSession;
  48. /**
  49. * Factory class for user model
  50. *
  51. * @var UserFactory
  52. */
  53. protected $userFactory;
  54. /**
  55. * Encryption model
  56. *
  57. * @var EncryptorInterface
  58. */
  59. protected $encryptor;
  60. /**
  61. * Message manager interface
  62. *
  63. * @var ManagerInterface
  64. */
  65. protected $messageManager;
  66. /**
  67. * @param ObserverConfig $observerConfig
  68. * @param ResourceUser $userResource
  69. * @param UrlInterface $url
  70. * @param Session $authSession
  71. * @param UserFactory $userFactory
  72. * @param EncryptorInterface $encryptor
  73. * @param ManagerInterface $messageManager
  74. */
  75. public function __construct(
  76. ObserverConfig $observerConfig,
  77. ResourceUser $userResource,
  78. UrlInterface $url,
  79. Session $authSession,
  80. UserFactory $userFactory,
  81. EncryptorInterface $encryptor,
  82. ManagerInterface $messageManager
  83. ) {
  84. $this->observerConfig = $observerConfig;
  85. $this->userResource = $userResource;
  86. $this->url = $url;
  87. $this->authSession = $authSession;
  88. $this->userFactory = $userFactory;
  89. $this->encryptor = $encryptor;
  90. $this->messageManager = $messageManager;
  91. }
  92. /**
  93. * Admin locking and password hashing upgrade logic implementation
  94. *
  95. * @param EventObserver $observer
  96. * @return void
  97. * @throws \Magento\Framework\Exception\LocalizedException
  98. */
  99. public function execute(EventObserver $observer)
  100. {
  101. $password = $observer->getEvent()->getPassword();
  102. /** @var User $user */
  103. $user = $observer->getEvent()->getUser();
  104. $authResult = $observer->getEvent()->getResult();
  105. if (!$authResult && $user->getId()) {
  106. // update locking information regardless whether user locked or not
  107. $this->_updateLockingInformation($user);
  108. }
  109. // check whether user is locked
  110. $lockExpires = $user->getLockExpires();
  111. if ($lockExpires) {
  112. $lockExpires = new \DateTime($lockExpires);
  113. if ($lockExpires > new \DateTime()) {
  114. throw new UserLockedException(
  115. __(
  116. 'The account sign-in was incorrect or your account is disabled temporarily. '
  117. . 'Please wait and try again later.'
  118. )
  119. );
  120. }
  121. }
  122. if (!$authResult) {
  123. return;
  124. }
  125. $this->userResource->unlock($user->getId());
  126. $latestPassword = $this->userResource->getLatestPassword($user->getId());
  127. $this->_checkExpiredPassword($latestPassword);
  128. if (!$this->encryptor->validateHashVersion($user->getPassword(), true)) {
  129. $user->setPassword($password)
  130. ->setData('force_new_password', true)
  131. ->save();
  132. }
  133. }
  134. /**
  135. * Update locking information for the user
  136. *
  137. * @param User $user
  138. * @return void
  139. */
  140. private function _updateLockingInformation($user)
  141. {
  142. $now = new \DateTime();
  143. $lockThreshold = $this->observerConfig->getAdminLockThreshold();
  144. $maxFailures = $this->observerConfig->getMaxFailures();
  145. if (!($lockThreshold && $maxFailures)) {
  146. return;
  147. }
  148. $failuresNum = (int)$user->getFailuresNum() + 1;
  149. /** @noinspection PhpAssignmentInConditionInspection */
  150. if ($firstFailureDate = $user->getFirstFailure()) {
  151. $firstFailureDate = new \DateTime($firstFailureDate);
  152. }
  153. $newFirstFailureDate = false;
  154. $updateLockExpires = false;
  155. $lockThreshInterval = new \DateInterval('PT' . $lockThreshold.'S');
  156. // set first failure date when this is first failure or last first failure expired
  157. if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThreshInterval) {
  158. $newFirstFailureDate = $now;
  159. // otherwise lock user
  160. } elseif ($failuresNum >= $maxFailures) {
  161. $updateLockExpires = $now->add($lockThreshInterval);
  162. }
  163. $this->userResource->updateFailure($user, $updateLockExpires, $newFirstFailureDate);
  164. }
  165. /**
  166. * Check whether the latest password is expired
  167. * Side-effect can be when passwords were changed with different lifetime configuration settings
  168. *
  169. * @param array $latestPassword
  170. * @return void
  171. */
  172. private function _checkExpiredPassword($latestPassword)
  173. {
  174. if ($latestPassword && $this->observerConfig->_isLatestPasswordExpired($latestPassword)) {
  175. if ($this->observerConfig->isPasswordChangeForced()) {
  176. $message = __('It\'s time to change your password.');
  177. } else {
  178. $myAccountUrl = $this->url->getUrl('adminhtml/system_account/');
  179. $message = __('It\'s time to <a href="%1">change your password</a>.', $myAccountUrl);
  180. }
  181. $messages = $this->messageManager->getMessages();
  182. // Remove existing messages with same ID to avoid duplication
  183. $messages->deleteMessageByIdentifier(User::MESSAGE_ID_PASSWORD_EXPIRED);
  184. $this->messageManager->addNoticeMessage($message);
  185. $message = $messages->getLastAddedMessage();
  186. if ($message) {
  187. $message->setIdentifier(User::MESSAGE_ID_PASSWORD_EXPIRED)->setIsSticky(true);
  188. $this->authSession->setPciAdminUserIsPasswordExpired(true);
  189. }
  190. }
  191. }
  192. }