diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php
index 8c02fcacecb6e..b2e8e0b98542f 100644
--- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php
+++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php
@@ -4,11 +4,12 @@
* See COPYING.txt for license details.
*/
+
+namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt;
+
/**
* Encryption key changer controller
*/
-namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt;
-
abstract class Key extends \Magento\Backend\App\Action
{
/**
diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php
index b294d98df9c1c..926d00e4f673b 100644
--- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php
+++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php
@@ -64,10 +64,10 @@ public function execute()
}
$newKey = $this->change->changeEncryptionKey($key);
- $this->messageManager->addSuccess(__('The encryption key has been changed.'));
+ $this->messageManager->addSuccessMessage(__('The encryption key has been changed.'));
if (!$key) {
- $this->messageManager->addNotice(
+ $this->messageManager->addNoticeMessage(
__(
'This is your new encryption key: %1. ' .
'Be sure to write it down and take good care of it!',
@@ -77,7 +77,7 @@ public function execute()
}
$this->cache->clean();
} catch (\Exception $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
$this->_session->setFormData(['crypt_key' => $key]);
}
$this->_redirect('adminhtml/*/');
diff --git a/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php
index 2ebb4e2cc3f8e..a211eb523b410 100644
--- a/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php
+++ b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php
@@ -77,35 +77,6 @@ protected function _construct()
$this->_init('core_config_data', 'config_id');
}
- /**
- * Re-encrypt all encrypted data in the database
- *
- * TODO: seems not used
- *
- * @param bool $safe Specifies whether wrapping re-encryption into the database transaction or not
- * @return void
- * @throws \Exception
- */
- public function reEncryptDatabaseValues($safe = true)
- {
- // update database only
- if ($safe) {
- $this->beginTransaction();
- }
- try {
- $this->_reEncryptSystemConfigurationValues();
- $this->_reEncryptCreditCardNumbers();
- if ($safe) {
- $this->commit();
- }
- } catch (\Exception $e) {
- if ($safe) {
- $this->rollBack();
- }
- throw $e;
- }
- }
-
/**
* Change encryption key
*
diff --git a/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php b/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php
new file mode 100644
index 0000000000000..ef30546eb3ba7
--- /dev/null
+++ b/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php
@@ -0,0 +1,130 @@
+encryptMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->changeMock = $this->getMockBuilder('Magento\EncryptionKey\Model\Resource\Key\Change')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->cacheMock = $this->getMockBuilder('Magento\Framework\App\CacheInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface')
+ ->disableOriginalConstructor()
+ ->setMethods(['getPost'])
+ ->getMockForAbstractClass();
+ $this->managerMock = $this->getMockBuilder('\Magento\Framework\Message\ManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->responseMock = $this->getMockBuilder('Magento\Framework\App\ResponseInterface')
+ ->disableOriginalConstructor()
+ ->setMethods(['setRedirect'])
+ ->getMockForAbstractClass();
+
+ $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->model = $helper->getObject(
+ 'Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key\Save',
+ [
+ 'encryptor' => $this->encryptMock,
+ 'change' => $this->changeMock,
+ 'cache' => $this->cacheMock,
+ 'request' => $this->requestMock,
+ 'messageManager' => $this->managerMock,
+ 'response' => $this->responseMock,
+ ]
+ );
+ }
+
+ public function testExecuteNonRandomAndWithCryptKey()
+ {
+ $expectedMessage = 'The encryption key has been changed.';
+ $key = 1;
+ $newKey = 'RSASHA9000VERYSECURESUPERMANKEY';
+ $this->requestMock
+ ->expects($this->at(0))
+ ->method('getPost')
+ ->with($this->equalTo('generate_random'))
+ ->willReturn(0);
+ $this->requestMock
+ ->expects($this->at(1))
+ ->method('getPost')
+ ->with($this->equalTo('crypt_key'))
+ ->willReturn($key);
+ $this->encryptMock->expects($this->once())->method('validateKey');
+ $this->changeMock->expects($this->once())->method('changeEncryptionKey')->willReturn($newKey);
+ $this->managerMock->expects($this->once())->method('addSuccessMessage')->with($expectedMessage);
+ $this->cacheMock->expects($this->once())->method('clean');
+ $this->responseMock->expects($this->once())->method('setRedirect');
+
+ $this->model->execute();
+ }
+
+ public function testExecuteNonRandomAndWithoutCryptKey()
+ {
+ $key = null;
+ $this->requestMock
+ ->expects($this->at(0))
+ ->method('getPost')
+ ->with($this->equalTo('generate_random'))
+ ->willReturn(0);
+ $this->requestMock
+ ->expects($this->at(1))
+ ->method('getPost')
+ ->with($this->equalTo('crypt_key'))
+ ->willReturn($key);
+ $this->managerMock->expects($this->once())->method('addErrorMessage');
+
+ $this->model->execute();
+ }
+
+ public function testExecuteRandom()
+ {
+ $newKey = 'RSASHA9000VERYSECURESUPERMANKEY';
+ $this->requestMock
+ ->expects($this->at(0))
+ ->method('getPost')
+ ->with($this->equalTo('generate_random'))
+ ->willReturn(1);
+ $this->changeMock->expects($this->once())->method('changeEncryptionKey')->willReturn($newKey);
+ $this->managerMock->expects($this->once())->method('addSuccessMessage');
+ $this->managerMock->expects($this->once())->method('addNoticeMessage');
+ $this->cacheMock->expects($this->once())->method('clean');
+ $this->responseMock->expects($this->once())->method('setRedirect');
+
+ $this->model->execute();
+ }
+}
diff --git a/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php b/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php
new file mode 100644
index 0000000000000..c0793ee43c544
--- /dev/null
+++ b/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php
@@ -0,0 +1,130 @@
+encryptMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface')
+ ->disableOriginalConstructor()
+ ->setMethods(['setNewKey', 'exportKeys'])
+ ->getMockForAbstractClass();
+ $this->filesystemMock = $this->getMockBuilder('Magento\Framework\Filesystem')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->structureMock = $this->getMockBuilder('Magento\Config\Model\Config\Structure')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->writerMock = $this->getMockBuilder('Magento\Framework\App\DeploymentConfig\Writer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->adapterMock = $this->getMockBuilder('Magento\Framework\DB\Adapter\AdapterInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->resourceMock = $this->getMockBuilder('Magento\Framework\App\Resource')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->selectMock = $this->getMockBuilder('Magento\Framework\DB\Select')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->tansactionMock = $this->getMockBuilder('Magento\Framework\Model\Resource\Db\TransactionManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->objRelationMock = $this->getMockBuilder('Magento\Framework\Model\Resource\Db\ObjectRelationProcessor')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->model = $helper->getObject(
+ 'Magento\EncryptionKey\Model\Resource\Key\Change',
+ [
+ 'filesystem' => $this->filesystemMock,
+ 'structure' => $this->structureMock,
+ 'encryptor' => $this->encryptMock,
+ 'writer' => $this->writerMock,
+ 'adapterInterface' => $this->adapterMock,
+ 'resource' => $this->resourceMock,
+ 'transactionManager' => $this->tansactionMock,
+ 'relationProcessor' => $this->objRelationMock
+ ]
+ );
+ }
+
+ public function testChangeEncryptionKey()
+ {
+ $paths = ['path1', 'path2'];
+ $table = ['item1', 'item2'];
+ $values = [
+ 'key1' => 'value1',
+ 'key2' => 'value2'
+ ];
+ $key = 'key';
+
+ $this->writerMock->expects($this->once())->method('checkIfWritable')->willReturn(true);
+ $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->adapterMock);
+ $this->adapterMock->expects($this->once())->method('beginTransaction');
+ $this->structureMock->expects($this->once())->method('getFieldPathsByAttribute')->willReturn($paths);
+ $this->resourceMock->expects($this->atLeastOnce())->method('getTableName')->willReturn($table);
+ $this->adapterMock->expects($this->any())->method('select')->willReturn($this->selectMock);
+ $this->adapterMock->expects($this->any())->method('fetchPairs')->willReturn($values);
+ $this->selectMock->expects($this->any())->method('from')->willReturnSelf();
+ $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturnSelf();
+ $this->selectMock->expects($this->any())->method('update')->willReturnSelf();
+ $this->writerMock->expects($this->once())->method('saveConfig');
+ $this->adapterMock->expects($this->once())->method('getTransactionLevel')->willReturn(1);
+
+ $this->assertEquals($key, $this->model->changeEncryptionKey($key));
+ }
+
+ public function testChangeEncryptionKeyThrowsException()
+ {
+ $key = 'key';
+ $this->writerMock->expects($this->once())->method('checkIfWritable')->willReturn(false);
+
+ try {
+ $this->model->changeEncryptionKey($key);
+ } catch (\Exception $e) {
+ return;
+ }
+
+ $this->fail('An excpected exception was not signaled.');
+ }
+}
diff --git a/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php b/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php
new file mode 100644
index 0000000000000..adcb28325dc53
--- /dev/null
+++ b/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php
@@ -0,0 +1,83 @@
+backendConfig = $backendConfig;
+ }
+
+ /**
+ * Check if latest password is expired
+ *
+ * @param array $latestPassword
+ * @return bool
+ */
+ public function _isLatestPasswordExpired($latestPassword)
+ {
+ if (!isset($latestPassword['expires']) || $this->getAdminPasswordLifetime() == 0) {
+ return false;
+ } else {
+ return (int)$latestPassword['expires'] < time();
+ }
+ }
+
+ /**
+ * Get admin lock threshold from configuration
+ * @return int
+ */
+ public function getAdminLockThreshold()
+ {
+ return 60 * (int)$this->backendConfig->getValue('admin/security/lockout_threshold');
+ }
+
+ /**
+ * Check whether password change is forced
+ *
+ * @return bool
+ */
+ public function isPasswordChangeForced()
+ {
+ return (bool)(int)$this->backendConfig->getValue('admin/security/password_is_forced');
+ }
+
+ /**
+ * Get admin password lifetime
+ *
+ * @return int
+ */
+ public function getAdminPasswordLifetime()
+ {
+ return 86400 * (int)$this->backendConfig->getValue('admin/security/password_lifetime');
+ }
+
+ /**
+ * Get admin maxiumum security failures from config
+ *
+ * @return int
+ */
+ public function getMaxFailures()
+ {
+ return (int)$this->backendConfig->getValue('admin/security/lockout_failures');
+ }
+}
diff --git a/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php b/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php
new file mode 100644
index 0000000000000..f682202a22320
--- /dev/null
+++ b/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php
@@ -0,0 +1,198 @@
+observerConfig = $observerConfig;
+ $this->userResource = $userResource;
+ $this->url = $url;
+ $this->authSession = $authSession;
+ $this->userFactory = $userFactory;
+ $this->encryptor = $encryptor;
+ $this->messageManager = $messageManager;
+ }
+
+ /**
+ * Admin locking and password hashing upgrade logic implementation
+ *
+ * @param EventObserver $observer
+ * @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function adminAuthenticate($observer)
+ {
+ $password = $observer->getEvent()->getPassword();
+ /** @var User $user */
+ $user = $observer->getEvent()->getUser();
+ $authResult = $observer->getEvent()->getResult();
+
+ if (!$authResult && $user->getId()) {
+ // update locking information regardless whether user locked or not
+ $this->_updateLockingInformation($user);
+ }
+
+ // check whether user is locked
+ $lockExpires = $user->getLockExpires();
+ if ($lockExpires) {
+ $lockExpires = new \DateTime($lockExpires);
+ if ($lockExpires > new \DateTime()) {
+ throw new UserLockedException(
+ __('You did not sign in correctly or your account is temporarily disabled.')
+ );
+ }
+ }
+
+ if (!$authResult) {
+ return;
+ }
+
+ $this->userResource->unlock($user->getId());
+
+ $latestPassword = $this->userResource->getLatestPassword($user->getId());
+ $this->_checkExpiredPassword($latestPassword);
+
+ if (!$this->encryptor->validateHashVersion($user->getPassword(), true)) {
+ $user->setPassword($password)
+ ->setData('force_new_password', true)
+ ->save();
+ }
+ }
+
+ /**
+ * Update locking information for the user
+ *
+ * @param \Magento\User\Model\User $user
+ * @return void
+ */
+ private function _updateLockingInformation($user)
+ {
+ $now = new \DateTime();
+ $lockThreshold = $this->observerConfig->getAdminLockThreshold();
+ $maxFailures = $this->observerConfig->getMaxFailures();
+ if (!($lockThreshold && $maxFailures)) {
+ return;
+ }
+ $failuresNum = (int)$user->getFailuresNum() + 1;
+ /** @noinspection PhpAssignmentInConditionInspection */
+ if ($firstFailureDate = $user->getFirstFailure()) {
+ $firstFailureDate = new \DateTime($firstFailureDate);
+ }
+
+ $newFirstFailureDate = false;
+ $updateLockExpires = false;
+ $lockThreshInterval = new \DateInterval('PT' . $lockThreshold.'S');
+ // set first failure date when this is first failure or last first failure expired
+ if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThreshInterval) {
+ $newFirstFailureDate = $now;
+ // otherwise lock user
+ } elseif ($failuresNum >= $maxFailures) {
+ $updateLockExpires = $now->add($lockThreshInterval);
+ }
+ $this->userResource->updateFailure($user, $updateLockExpires, $newFirstFailureDate);
+ }
+
+ /**
+ * Check whether the latest password is expired
+ * Side-effect can be when passwords were changed with different lifetime configuration settings
+ *
+ * @param array $latestPassword
+ * @return void
+ */
+ private function _checkExpiredPassword($latestPassword)
+ {
+ if ($latestPassword && $this->observerConfig->_isLatestPasswordExpired($latestPassword)) {
+ if ($this->observerConfig->isPasswordChangeForced()) {
+ $message = __('It\'s time to change your password.');
+ } else {
+ $myAccountUrl = $this->url->getUrl('adminhtml/system_account/');
+ $message = __('It\'s time to change your password.', $myAccountUrl);
+ }
+ $this->messageManager->addNoticeMessage($message);
+ $message = $this->messageManager->getMessages()->getLastAddedMessage();
+ if ($message) {
+ $message->setIdentifier('magento_user_password_expired')->setIsSticky(true);
+ $this->authSession->setPciAdminUserIsPasswordExpired(true);
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/User/Model/Backend/Observer.php b/app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php
similarity index 52%
rename from app/code/Magento/User/Model/Backend/Observer.php
rename to app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php
index 95e5259b53850..93c54fd5df9a7 100644
--- a/app/code/Magento/User/Model/Backend/Observer.php
+++ b/app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php
@@ -3,33 +3,29 @@
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-namespace Magento\User\Model\Backend;
+
+namespace Magento\User\Model\Backend\Observer;
use Magento\Framework\Event\Observer as EventObserver;
-use Magento\Framework\Exception\State\UserLockedException;
-use Magento\User\Model\User;
/**
- * User backend observer model
- *
- * Implements hashes upgrading
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * User backend observer model for passwords
*/
-class Observer
+class PasswordObserver
{
/**
- * Authorization interface
+ * Backend configuration interface
*
- * @var \Magento\Framework\AuthorizationInterface
+ * @var \Magento\User\Model\Backend\Config\ObserverConfig
*/
- protected $authorization;
+ protected $observerConfig;
/**
- * Backend configuration interface
+ * Authorization interface
*
- * @var \Magento\Backend\App\ConfigInterface
+ * @var \Magento\Framework\AuthorizationInterface
*/
- protected $backendConfig;
+ protected $authorization;
/**
* Admin user resource model
@@ -59,13 +55,6 @@ class Observer
*/
protected $authSession;
- /**
- * Factory class for user model
- *
- * @var \Magento\User\Model\UserFactory
- */
- protected $userFactory;
-
/**
* Encryption model
*
@@ -89,159 +78,37 @@ class Observer
/**
* @param \Magento\Framework\AuthorizationInterface $authorization
- * @param \Magento\Backend\App\ConfigInterface $backendConfig
+ * @param \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig
* @param \Magento\User\Model\Resource\User $userResource
* @param \Magento\Backend\Model\UrlInterface $url
* @param \Magento\Backend\Model\Session $session
* @param \Magento\Backend\Model\Auth\Session $authSession
- * @param \Magento\User\Model\UserFactory $userFactory
* @param \Magento\Framework\Encryption\EncryptorInterface $encryptor
* @param \Magento\Framework\App\ActionFlag $actionFlag
* @param \Magento\Framework\Message\ManagerInterface $messageManager
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\AuthorizationInterface $authorization,
- \Magento\Backend\App\ConfigInterface $backendConfig,
+ \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig,
\Magento\User\Model\Resource\User $userResource,
\Magento\Backend\Model\UrlInterface $url,
\Magento\Backend\Model\Session $session,
\Magento\Backend\Model\Auth\Session $authSession,
- \Magento\User\Model\UserFactory $userFactory,
\Magento\Framework\Encryption\EncryptorInterface $encryptor,
\Magento\Framework\App\ActionFlag $actionFlag,
\Magento\Framework\Message\ManagerInterface $messageManager
) {
$this->authorization = $authorization;
- $this->backendConfig = $backendConfig;
+ $this->observerConfig = $observerConfig;
$this->userResource = $userResource;
$this->url = $url;
$this->session = $session;
$this->authSession = $authSession;
- $this->userFactory = $userFactory;
$this->encryptor = $encryptor;
$this->actionFlag = $actionFlag;
$this->messageManager = $messageManager;
}
- /**
- * Admin locking and password hashing upgrade logic implementation
- *
- * @param EventObserver $observer
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- public function adminAuthenticate($observer)
- {
- $password = $observer->getEvent()->getPassword();
- /** @var User $user */
- $user = $observer->getEvent()->getUser();
- $authResult = $observer->getEvent()->getResult();
-
- if (!$authResult && $user->getId()) {
- // update locking information regardless whether user locked or not
- $this->_updateLockingInformation($user);
- }
-
- // check whether user is locked
- $lockExpires = $user->getLockExpires();
- if ($lockExpires) {
- $lockExpires = new \DateTime($lockExpires);
- if ($lockExpires > new \DateTime()) {
- throw new UserLockedException(
- __('You did not sign in correctly or your account is temporarily disabled.')
- );
- }
- }
-
- if (!$authResult) {
- return;
- }
-
- $this->userResource->unlock($user->getId());
-
- $latestPassword = $this->userResource->getLatestPassword($user->getId());
- $this->_checkExpiredPassword($latestPassword);
-
- if (!$this->encryptor->validateHashVersion($user->getPassword(), true)) {
- $user->setPassword($password)
- ->setData('force_new_password', true)
- ->save();
- }
- }
-
- /**
- * Update locking information for the user
- *
- * @param \Magento\User\Model\User $user
- * @return void
- */
- private function _updateLockingInformation($user)
- {
- $now = new \DateTime();
- $lockThreshold = $this->getAdminLockThreshold();
- $maxFailures = (int)$this->backendConfig->getValue('admin/security/lockout_failures');
- if (!($lockThreshold && $maxFailures)) {
- return;
- }
- $failuresNum = (int)$user->getFailuresNum() + 1;
- if ($firstFailureDate = $user->getFirstFailure()) {
- $firstFailureDate = new \DateTime($firstFailureDate);
- }
-
- $updateFirstFailureDate = false;
- $updateLockExpires = false;
- $lockThresholdInterval = new \DateInterval('PT' . $lockThreshold.'S');
- // set first failure date when this is first failure or last first failure expired
- if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThresholdInterval) {
- $updateFirstFailureDate = $now;
- // otherwise lock user
- } elseif ($failuresNum >= $maxFailures) {
- $updateLockExpires = $now->add($lockThresholdInterval);
- }
- $this->userResource->updateFailure($user, $updateLockExpires, $updateFirstFailureDate);
- }
-
- /**
- * Check whether the latest password is expired
- * Side-effect can be when passwords were changed with different lifetime configuration settings
- *
- * @param array $latestPassword
- * @return void
- */
- private function _checkExpiredPassword($latestPassword)
- {
- if ($latestPassword && $this->_isLatestPasswordExpired($latestPassword)) {
- if ($this->isPasswordChangeForced()) {
- $message = __('It\'s time to change your password.');
- } else {
- $myAccountUrl = $this->url->getUrl('adminhtml/system_account/');
- $message = __('It\'s time to change your password.', $myAccountUrl);
- }
- $this->messageManager->addNotice($message);
- $message = $this->messageManager->getMessages()->getLastAddedMessage();
- if ($message) {
- $message->setIdentifier('magento_user_password_expired')->setIsSticky(true);
- $this->authSession->setPciAdminUserIsPasswordExpired(true);
- }
- }
- }
-
- /**
- * Check if latest password is expired
- *
- * @param array $latestPassword
- * @return bool
- */
- protected function _isLatestPasswordExpired($latestPassword)
- {
- if (!isset($latestPassword['expires']) || $this->getAdminPasswordLifetime() == 0) {
- return false;
- } else {
- return (int)$latestPassword['expires'] < time();
- }
- }
-
/**
* Harden admin password change.
*
@@ -264,7 +131,7 @@ public function checkAdminPasswordChange($observer)
}
if ($password && !$user->getForceNewPassword() && $user->getId()) {
- if ($this->encryptor->validateHash($password, $user->getOrigData('password'))) {
+ if ($this->encryptor->isValidHash($password, $user->getOrigData('password'))) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Sorry, but this password has already been used. Please create another.')
);
@@ -295,7 +162,7 @@ public function trackAdminNewPassword($observer)
$user = $observer->getEvent()->getObject();
if ($user->getId()) {
$password = $user->getNewPassword();
- $passwordLifetime = $this->getAdminPasswordLifetime();
+ $passwordLifetime = $this->observerConfig->getAdminPasswordLifetime();
if ($passwordLifetime && $password && !$user->getForceNewPassword()) {
$resource = $this->userResource;
$passwordHash = $this->encryptor->getHash($password, false);
@@ -306,25 +173,6 @@ public function trackAdminNewPassword($observer)
}
}
- /**
- * Get admin lock threshold from configuration
- * @return int
- */
- public function getAdminLockThreshold()
- {
- return 60 * (int)$this->backendConfig->getValue('admin/security/lockout_threshold');
- }
-
- /**
- * Get admin password lifetime
- *
- * @return int
- */
- public function getAdminPasswordLifetime()
- {
- return 86400 * (int)$this->backendConfig->getValue('admin/security/password_lifetime');
- }
-
/**
* Force admin to change password
*
@@ -333,11 +181,10 @@ public function getAdminPasswordLifetime()
*/
public function forceAdminPasswordChange($observer)
{
- if (!$this->isPasswordChangeForced()) {
+ if (!$this->observerConfig->isPasswordChangeForced()) {
return;
}
- $session = $this->authSession;
- if (!$session->isLoggedIn()) {
+ if (!$this->authSession->isLoggedIn()) {
return;
}
$actionList = [
@@ -345,9 +192,11 @@ public function forceAdminPasswordChange($observer)
'adminhtml_system_account_save',
'adminhtml_auth_logout',
];
+ /** @var \Magento\Framework\App\Action\Action $controller */
$controller = $observer->getEvent()->getControllerAction();
/** @var \Magento\Framework\App\RequestInterface $request */
$request = $observer->getEvent()->getRequest();
+
if ($this->authSession->getPciAdminUserIsPasswordExpired()) {
if (!in_array($request->getFullActionName(), $actionList)) {
if ($this->authorization->isAllowed('Magento_Backend::myaccount')) {
@@ -361,7 +210,7 @@ public function forceAdminPasswordChange($observer)
*/
$this->authSession->clearStorage();
$this->session->clearStorage();
- $this->messageManager->addError(
+ $this->messageManager->addErrorMessage(
__('Your password has expired; please contact your administrator.')
);
$controller->getRequest()->setDispatched(false);
@@ -369,14 +218,4 @@ public function forceAdminPasswordChange($observer)
}
}
}
-
- /**
- * Check whether password change is forced
- *
- * @return bool
- */
- public function isPasswordChangeForced()
- {
- return (bool)(int)$this->backendConfig->getValue('admin/security/password_is_forced');
- }
}
diff --git a/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php
new file mode 100644
index 0000000000000..97be558ef833b
--- /dev/null
+++ b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php
@@ -0,0 +1,267 @@
+configInterfaceMock = $this->getMockBuilder('Magento\Backend\App\ConfigInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->userMock = $this->getMockBuilder('Magento\User\Model\Resource\User')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->urlInterfaceMock = $this->getMockBuilder('Magento\Backend\Model\UrlInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session')
+ ->disableOriginalConstructor()
+ ->setMethods(
+ [
+ 'setPciAdminUserIsPasswordExpired',
+ 'unsPciAdminUserIsPasswordExpired',
+ 'getPciAdminUserIsPasswordExpired',
+ 'isLoggedIn',
+ 'clearStorage'
+ ]
+ )->getMock();
+
+ $this->userFactoryMock = $this->getMockBuilder('Magento\User\Model\UserFactory')
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+
+ $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\EncryptorInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->managerInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->messageInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\MessageInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\ManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMockForAbstractClass();
+
+ $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->observerConfig = $helper->getObject(
+ 'Magento\User\Model\Backend\Config\ObserverConfig',
+ [
+ 'backendConfig' => $this->configInterfaceMock
+ ]
+ );
+
+ $this->model = $helper->getObject(
+ 'Magento\User\Model\Backend\Observer\AuthObserver',
+ [
+ 'observerConfig' => $this->observerConfig,
+ 'userResource' => $this->userMock,
+ 'url' => $this->urlInterfaceMock,
+ 'authSession' => $this->authSessionMock,
+ 'userFactory' => $this->userFactoryMock,
+ 'encryptor' => $this->encryptorMock,
+ 'messageManager' => $this->managerInterfaceMock,
+ 'messageInterface' => $this->messageInterfaceMock,
+ 'eventManager' => $this->eventManagerMock
+ ]
+ );
+ }
+
+ public function testAdminAuthenticate()
+ {
+ $password = "myP@sw0rd";
+ $uid = 123;
+ $authResult = true;
+ $lockExpires = false;
+ $userPassword = [
+ 'expires' => 1,
+ ];
+
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getPassword', 'getUser', 'getResult'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getLockExpires', 'getPassword', 'save'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getPassword')->willReturn($password);
+ $eventMock->expects($this->once())->method('getUser')->willReturn($userMock);
+ $eventMock->expects($this->once())->method('getResult')->willReturn($authResult);
+ $userMock->expects($this->atLeastOnce())->method('getId')->willReturn($uid);
+ $userMock->expects($this->once())->method('getLockExpires')->willReturn($lockExpires);
+ $this->userMock->expects($this->once())->method('unlock');
+ $this->userMock->expects($this->once())->method('getLatestPassword')->willReturn($userPassword);
+ $this->configInterfaceMock
+ ->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn(1);
+
+ /** @var \Magento\Framework\Message\Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */
+ $collectionMock = $this->getMockBuilder('Magento\Framework\Message\Collection')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->managerInterfaceMock->expects($this->once())->method('getMessages')->willReturn($collectionMock);
+ $collectionMock
+ ->expects($this->once())
+ ->method('getLastAddedMessage')
+ ->willReturn($this->messageInterfaceMock);
+ $this->messageInterfaceMock->expects($this->once())->method('setIdentifier')->willReturnSelf();
+ $this->authSessionMock->expects($this->once())->method('setPciAdminUserIsPasswordExpired');
+ $this->encryptorMock->expects($this->once())->method('validateHashVersion')->willReturn(false);
+
+ $this->model->adminAuthenticate($eventObserverMock);
+ }
+
+ public function testAdminAuthenticateThrowsException()
+ {
+ $password = "myP@sw0rd";
+ $authResult = true;
+ $lockExpires = '3015-07-08 11:14:15.638276';
+
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getPassword', 'getUser', 'getResult'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getLockExpires', 'getPassword'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getPassword')->willReturn($password);
+ $eventMock->expects($this->once())->method('getUser')->willReturn($userMock);
+ $eventMock->expects($this->once())->method('getResult')->willReturn($authResult);
+ $userMock->expects($this->once())->method('getLockExpires')->willReturn($lockExpires);
+
+ try {
+ $this->model->adminAuthenticate($eventObserverMock);
+ } catch (UserLockedException $expected) {
+ return;
+ }
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ public function testAdminAuthenticateUpdateLockingInfo()
+ {
+ $password = "myP@sw0rd";
+ $uid = 123;
+ $authResult = false;
+ $firstFailure = '1965-07-08 11:14:15.638276';
+ $numOfFailures = 5;
+
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getPassword', 'getUser', 'getResult'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getFailuresNum', 'getFirstFailure'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getPassword')->willReturn($password);
+ $eventMock->expects($this->once())->method('getUser')->willReturn($userMock);
+ $eventMock->expects($this->once())->method('getResult')->willReturn($authResult);
+ $userMock->expects($this->once())->method('getId')->willReturn($uid);
+ $this->configInterfaceMock
+ ->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn(1);
+ $userMock->expects($this->once())->method('getFailuresNum')->willReturn($numOfFailures);
+ $userMock->expects($this->once())->method('getFirstFailure')->willReturn($firstFailure);
+ $this->userMock->expects($this->once())->method('updateFailure');
+
+ $this->model->adminAuthenticate($eventObserverMock);
+ }
+}
diff --git a/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php
new file mode 100644
index 0000000000000..331d74ac086d3
--- /dev/null
+++ b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php
@@ -0,0 +1,308 @@
+authMock = $this->getMockBuilder('Magento\Framework\AuthorizationInterface')
+ ->disableOriginalConstructor()
+ ->setMethods(['isAllowed'])
+ ->getMock();
+
+ $this->configInterfaceMock = $this->getMockBuilder('Magento\Backend\App\ConfigInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->userMock = $this->getMockBuilder('Magento\User\Model\Resource\User')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->urlInterfaceMock = $this->getMockBuilder('Magento\Backend\Model\UrlInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->sessionMock = $this->getMockBuilder('Magento\Backend\Model\Session')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session')
+ ->disableOriginalConstructor()
+ ->setMethods(
+ [
+ 'setPciAdminUserIsPasswordExpired',
+ 'unsPciAdminUserIsPasswordExpired',
+ 'getPciAdminUserIsPasswordExpired',
+ 'isLoggedIn',
+ 'clearStorage'
+ ]
+ )->getMock();
+
+ $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\EncryptorInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->actionFlagMock = $this->getMockBuilder('Magento\Framework\App\ActionFlag')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->managerInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->messageInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\MessageInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\ManagerInterface')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMockForAbstractClass();
+
+ $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->observerConfig = $helper->getObject(
+ 'Magento\User\Model\Backend\Config\ObserverConfig',
+ [
+ 'backendConfig' => $this->configInterfaceMock
+ ]
+ );
+
+ $this->model = $helper->getObject(
+ 'Magento\User\Model\Backend\Observer\PasswordObserver',
+ [
+ 'observerConfig' => $this->observerConfig,
+ 'authorization' => $this->authMock,
+ 'userResource' => $this->userMock,
+ 'url' => $this->urlInterfaceMock,
+ 'session' => $this->sessionMock,
+ 'authSession' => $this->authSessionMock,
+ 'actionFlag' => $this->actionFlagMock,
+ 'encryptor' => $this->encryptorMock,
+ 'messageManager' => $this->managerInterfaceMock,
+ 'messageInterface' => $this->messageInterfaceMock,
+ 'eventManager' => $this->eventManagerMock
+ ]
+ );
+ }
+
+ public function testCheckAdminPasswordChange()
+ {
+ $newPW = "mYn3wpassw0rd";
+ $uid = 123;
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getObject'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getObject')->willReturn($userMock);
+ $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW);
+ $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false);
+ $userMock->expects($this->once())->method('getId')->willReturn($uid);
+ $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(false);
+ $this->encryptorMock->expects($this->once())->method('getHash')->willReturn(md5($newPW));
+ $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]);
+
+ $this->model->checkAdminPasswordChange($eventObserverMock);
+ }
+
+ public function testCheckAdminPasswordChangeThrowsLocalizedExp()
+ {
+ $newPW = "mYn3wpassw0rd";
+ $uid = 123;
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getObject'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getObject')->willReturn($userMock);
+ $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW);
+ $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false);
+ $userMock->expects($this->once())->method('getId')->willReturn($uid);
+ $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(true);
+ $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]);
+
+ try {
+ $this->model->checkAdminPasswordChange($eventObserverMock);
+ } catch (\Magento\Framework\Exception\LocalizedException $expected) {
+ return;
+ }
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ public function testTrackAdminPassword()
+ {
+ $newPW = "mYn3wpassw0rd";
+ $oldPW = "notsecure";
+ $uid = 123;
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getObject'])
+ ->getMock();
+
+ /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */
+ $userMock = $this->getMockBuilder('Magento\User\Model\User')
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword'])
+ ->getMock();
+
+ $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock);
+ $eventMock->expects($this->once())->method('getObject')->willReturn($userMock);
+ $userMock->expects($this->once())->method('getId')->willReturn($uid);
+ $userMock->expects($this->once())->method('getNewPassword')->willReturn($newPW);
+ $this->configInterfaceMock
+ ->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn(1);
+ $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false);
+ $this->encryptorMock->expects($this->once())->method('getHash')->willReturn(md5($oldPW));
+
+ /** @var \Magento\Framework\Message\Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */
+ $collectionMock = $this->getMockBuilder('Magento\Framework\Message\Collection')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+ $this->managerInterfaceMock
+ ->expects($this->once())
+ ->method('getMessages')
+ ->willReturn($collectionMock);
+ $this->authSessionMock->expects($this->once())->method('unsPciAdminUserIsPasswordExpired')->willReturn(null);
+
+ $this->model->trackAdminNewPassword($eventObserverMock);
+ }
+
+ public function testForceAdminPasswordChange()
+ {
+ /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */
+ $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer')
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */
+ $eventMock = $this->getMockBuilder('Magento\Framework\Event')
+ ->disableOriginalConstructor()
+ ->setMethods(['getControllerAction', 'getRequest'])
+ ->getMock();
+
+ $this->configInterfaceMock
+ ->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn(1);
+ $this->authSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(true);
+ $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock);
+ /** @var \Magento\Framework\App\Action\Action $controllerMock */
+ $controllerMock = $this->getMockBuilder('Magento\Framework\App\Action\AbstractAction')
+ ->disableOriginalConstructor()
+ ->setMethods(['getRedirect', 'getRequest'])
+ ->getMockForAbstractClass();
+ /** @var \Magento\Framework\App\RequestInterface $requestMock */
+ $requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface')
+ ->disableOriginalConstructor()
+ ->setMethods(['getFullActionName', 'setDispatched'])
+ ->getMockForAbstractClass();
+ $eventMock->expects($this->once())->method('getControllerAction')->willReturn($controllerMock);
+ $eventMock->expects($this->once())->method('getRequest')->willReturn($requestMock);
+ $this->authSessionMock->expects($this->once())->method('getPciAdminUserIsPasswordExpired')->willReturn(true);
+ $requestMock->expects($this->once())->method('getFullActionName')->willReturn('not_in_array');
+
+ $this->authSessionMock->expects($this->once())->method('clearStorage');
+ $this->sessionMock->expects($this->once())->method('clearStorage');
+ $this->managerInterfaceMock->expects($this->once())->method('addErrorMessage');
+ $controllerMock->expects($this->once())->method('getRequest')->willReturn($requestMock);
+ $requestMock->expects($this->once())->method('setDispatched')->willReturn(false);
+
+ $this->model->forceAdminPasswordChange($eventObserverMock);
+ }
+}
diff --git a/app/code/Magento/User/etc/adminhtml/di.xml b/app/code/Magento/User/etc/adminhtml/di.xml
old mode 100644
new mode 100755
index ad550cbf5058d..4278e618075fa
--- a/app/code/Magento/User/etc/adminhtml/di.xml
+++ b/app/code/Magento/User/etc/adminhtml/di.xml
@@ -7,7 +7,7 @@
-->
-
+
Magento\Framework\Authorization
diff --git a/app/code/Magento/User/etc/adminhtml/events.xml b/app/code/Magento/User/etc/adminhtml/events.xml
old mode 100644
new mode 100755
index 01f8142a549d0..3bdfe2f33f7e6
--- a/app/code/Magento/User/etc/adminhtml/events.xml
+++ b/app/code/Magento/User/etc/adminhtml/events.xml
@@ -7,15 +7,15 @@
-->
-
+
-
+
-
+
-
+
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml
new file mode 100644
index 0000000000000..8101677711e3e
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ - admin
+ - 1
+ - 6
+ - 6
+
+
+
+
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php
new file mode 100644
index 0000000000000..d9653f0c2c7ce
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php
@@ -0,0 +1,72 @@
+persist();
+ $customAdmin->persist();
+ /** @var User $incorrectUser */
+ $incorrectUser = $fixtureFactory->createByCode(
+ 'user',
+ ['data' => ['username' => $customAdmin->getUsername(), 'password' => $incorrectPassword]]
+ );
+
+ // Steps and assertions
+ for ($i = 0; $i < $attempts; $i++) {
+ $assertUserFailedLoginMessage->processAssert($adminAuth, $incorrectUser);
+ }
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml
new file mode 100644
index 0000000000000..5fda7b00a447b
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ default_lockout_failures
+
+ -
+
- 6
+
+
+ custom_admin_with_default_role
+ honey boo boo
+ 7
+
+
+
+
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml
index 692af412394d2..aeac825861854 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml
@@ -7,6 +7,16 @@
-->
+
+ System > Locked Users
+ Locked Users
+
+
+
+ System > Manage Encryption Key
+ Encryption Key
+
+
System > All Users
Users
diff --git a/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php b/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php
index 0ebf3de8ec0b7..eeb5ae06c6339 100644
--- a/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php
+++ b/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php
@@ -7,6 +7,7 @@
/**
* @magentoAppArea adminhtml
+ * @magentoAppIsolation enabled
*/
class DataTest extends \PHPUnit_Framework_TestCase
{
diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php
index 3f0e20d8a744b..b8d78918bb5e7 100644
--- a/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php
+++ b/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php
@@ -11,6 +11,7 @@
* Test class for \Magento\Backend\Model\Auth.
*
* @magentoAppArea adminhtml
+ * @magentoAppIsolation enabled
*/
class AuthTest extends \PHPUnit_Framework_TestCase
{
diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php
index bef1e03ea68ee..731836d648132 100644
--- a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php
+++ b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php
@@ -254,17 +254,17 @@ public function cssDirectiveDataProvider()
'CSS from theme' => [
TemplateTypesInterface::TYPE_HTML,
'file="css/email-1.css"',
- 'color: #111;'
+ 'color: #111'
],
'CSS from parent theme' => [
TemplateTypesInterface::TYPE_HTML,
'file="css/email-2.css"',
- 'color: #222;'
+ 'color: #222'
],
'CSS from grandparent theme' => [
TemplateTypesInterface::TYPE_HTML,
'file="css/email-3.css"',
- 'color: #333;'
+ 'color: #333'
],
'Missing file parameter' => [
TemplateTypesInterface::TYPE_HTML,
@@ -316,10 +316,8 @@ public function testInlinecssDirective(
$this->model->setPlainTemplateMode($plainTemplateMode);
$this->model->setIsChildTemplate($isChildTemplateMode);
- if ($productionMode) {
- $this->objectManager->get('Magento\Framework\App\State')
- ->setMode(State::MODE_PRODUCTION);
- }
+ $appMode = $productionMode ? State::MODE_PRODUCTION : State::MODE_DEVELOPER;
+ $this->objectManager->get('Magento\Framework\App\State')->setMode($appMode);
$this->assertContains($expectedOutput, $this->model->filter($templateText));
}
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php
index b81c11211dbbe..e1c319834a5c9 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php
@@ -11,11 +11,16 @@
use Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory;
use Magento\Framework\Reflection\TypeProcessor;
use Magento\Framework\App\Resource as AppResource;
+use Magento\Framework\Api\ExtensionAttributesFactory;
+/**
+ * Class to test the JoinProcessor functionality
+ */
class JoinProcessorTest extends \PHPUnit_Framework_TestCase
{
-
- /** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor */
+ /**
+ * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor
+ */
private $joinProcessor;
/**
@@ -38,6 +43,16 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase
*/
private $appResource;
+ /**
+ * @var ExtensionAttributesFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $extensionAttributesFactory;
+
+ /**
+ * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper
+ */
+ private $joinProcessorHelper;
+
protected function setUp()
{
$this->config = $this->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\Config')
@@ -47,11 +62,10 @@ protected function setUp()
->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory')
->disableOriginalConstructor()
->getMock();
- $this->extensionAttributeJoinDataFactory = $this
- ->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory')
+ $this->typeProcessor = $this->getMockBuilder('Magento\Framework\Reflection\TypeProcessor')
->disableOriginalConstructor()
->getMock();
- $this->typeProcessor = $this->getMockBuilder('Magento\Framework\Reflection\TypeProcessor')
+ $this->extensionAttributesFactory = $this->getMockBuilder('Magento\Framework\Api\ExtensionAttributesFactory')
->disableOriginalConstructor()
->getMock();
@@ -60,13 +74,20 @@ protected function setUp()
$this->appResource = $objectManager->get('Magento\Framework\App\Resource');
+ $this->joinProcessorHelper = $objectManager->create(
+ 'Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper',
+ [
+ 'config' => $this->config,
+ 'joinDataInterfaceFactory' => $this->extensionAttributeJoinDataFactory
+ ]
+ );
+
$this->joinProcessor = $objectManager->create(
'Magento\Framework\Api\ExtensionAttribute\JoinProcessor',
[
'objectManager' => $objectManager,
- 'config' => $this->config,
- 'extensionAttributeJoinDataFactory' => $this->extensionAttributeJoinDataFactory,
- 'typeProcessor' => $this->typeProcessor
+ 'typeProcessor' => $this->typeProcessor,
+ 'joinProcessorHelper' => $this->joinProcessorHelper
]
);
}
@@ -81,8 +102,8 @@ public function testProcess()
->will($this->returnValue($this->getConfig()));
$collection = $this->getMockBuilder('Magento\Framework\Data\Collection\AbstractDb')
- ->setMethods(['joinExtensionAttribute'])
->disableOriginalConstructor()
+ ->setMethods(['joinExtensionAttribute'])
->getMockForAbstractClass();
$extensionAttributeJoinData = new JoinData();
@@ -112,6 +133,11 @@ public function testProcess()
);
}
+ /**
+ * Will return the data that is expected from the config object
+ *
+ * @return array
+ */
private function getConfig()
{
return [
@@ -194,10 +220,17 @@ public function testProcessSqlSelectVerification()
'Magento\Framework\Api\ExtensionAttribute\Config',
['reader' => $configReader]
);
+
+ /** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper $extensionAttributesProcessorHelper */
+ $extensionAttributesProcessorHelper = $objectManager->create(
+ 'Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper',
+ ['config' => $config]
+ );
+
/** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor $extensionAttributesProcessor */
$extensionAttributesProcessor = $objectManager->create(
'Magento\Framework\Api\ExtensionAttribute\JoinProcessor',
- ['config' => $config]
+ ['joinProcessorHelper' => $extensionAttributesProcessorHelper]
);
/** @var \Magento\Catalog\Model\Resource\Product\Collection $collection */
$collection = $objectManager->create('Magento\Catalog\Model\Resource\Product\Collection');
diff --git a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php
index dbe83d1d343a2..3f7ebf4ab4ae4 100644
--- a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php
+++ b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php
@@ -7,18 +7,14 @@
namespace Magento\Framework\Api\ExtensionAttribute;
use Magento\Framework\Api\ExtensionAttribute\Config;
-use Magento\Framework\Api\ExtensionAttribute\Config\Converter;
+use Magento\Framework\Api\ExtensionAttribute\Config\Converter as Converter;
use Magento\Framework\Data\Collection\AbstractDb as DbCollection;
-use Magento\Framework\Api\ExtensionAttribute\JoinDataInterface;
-use Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory;
use Magento\Framework\Reflection\TypeProcessor;
use Magento\Framework\Api\ExtensibleDataInterface;
use Magento\Framework\Api\ExtensionAttributesFactory;
-use Magento\Framework\Api\SimpleDataObjectConverter;
/**
* Join processor allows to join extension attributes during collections loading.
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface
{
@@ -29,47 +25,33 @@ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinPro
*/
protected $objectManager;
- /**
- * @var Config
- */
- private $config;
-
- /**
- * @var JoinDataInterfaceFactory
- */
- private $extensionAttributeJoinDataFactory;
-
- /**
- * @var TypeProcessor
- */
+ /** @var TypeProcessor */
private $typeProcessor;
- /**
- * @var ExtensionAttributesFactory
- */
+ /** @var ExtensionAttributesFactory */
private $extensionAttributesFactory;
+ /** @var JoinProcessorHelper */
+ private $joinProcessorHelper;
+
/**
* Initialize dependencies.
*
* @param \Magento\Framework\ObjectManagerInterface $objectManager
- * @param Config $config
- * @param JoinDataInterfaceFactory $extensionAttributeJoinDataFactory
* @param TypeProcessor $typeProcessor
* @param ExtensionAttributesFactory $extensionAttributesFactory
+ * @param JoinProcessorHelper $joinProcessorHelper
*/
public function __construct(
\Magento\Framework\ObjectManagerInterface $objectManager,
- Config $config,
- JoinDataInterfaceFactory $extensionAttributeJoinDataFactory,
TypeProcessor $typeProcessor,
- ExtensionAttributesFactory $extensionAttributesFactory
+ ExtensionAttributesFactory $extensionAttributesFactory,
+ JoinProcessorHelper $joinProcessorHelper
) {
$this->objectManager = $objectManager;
- $this->config = $config;
- $this->extensionAttributeJoinDataFactory = $extensionAttributeJoinDataFactory;
$this->typeProcessor = $typeProcessor;
$this->extensionAttributesFactory = $extensionAttributesFactory;
+ $this->joinProcessorHelper = $joinProcessorHelper;
}
/**
@@ -79,50 +61,22 @@ public function process(DbCollection $collection, $extensibleEntityClass = null)
{
$extensibleEntityClass = $extensibleEntityClass ?: $collection->getItemObjectClass();
$joinDirectives = $this->getJoinDirectivesForType($extensibleEntityClass);
+
foreach ($joinDirectives as $attributeCode => $directive) {
/** @var JoinDataInterface $joinData */
- $joinData = $this->extensionAttributeJoinDataFactory->create();
+ $joinData = $this->joinProcessorHelper->getJoinDataInterface();
$joinData->setAttributeCode($attributeCode)
->setReferenceTable($directive[Converter::JOIN_REFERENCE_TABLE])
->setReferenceTableAlias($this->getReferenceTableAlias($attributeCode))
->setReferenceField($directive[Converter::JOIN_REFERENCE_FIELD])
->setJoinField($directive[Converter::JOIN_ON_FIELD]);
$joinData->setSelectFields(
- $this->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS])
+ $this->joinProcessorHelper->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS])
);
$collection->joinExtensionAttribute($joinData, $this);
}
}
- /**
- * Generate a list of select fields with mapping of client facing attribute names to field names used in SQL select.
- *
- * @param string $attributeCode
- * @param array $selectFields
- * @return array
- */
- private function getSelectFieldsMap($attributeCode, $selectFields)
- {
- $referenceTableAlias = $this->getReferenceTableAlias($attributeCode);
- $useFieldInAlias = (count($selectFields) > 1);
- $selectFieldsAliases = [];
- foreach ($selectFields as $selectField) {
- $internalFieldName = $selectField[Converter::JOIN_FIELD_COLUMN]
- ? $selectField[Converter::JOIN_FIELD_COLUMN]
- : $selectField[Converter::JOIN_FIELD];
- $setterName = 'set'
- . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($selectField[Converter::JOIN_FIELD]));
- $selectFieldsAliases[] = [
- JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS => $attributeCode
- . ($useFieldInAlias ? '.' . $selectField[Converter::JOIN_FIELD] : ''),
- JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS => $referenceTableAlias . '_' . $internalFieldName,
- JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX => $referenceTableAlias . '.' . $internalFieldName,
- JoinDataInterface::SELECT_FIELD_SETTER => $setterName
- ];
- }
- return $selectFieldsAliases;
- }
-
/**
* Generate reference table alias.
*
@@ -180,7 +134,9 @@ private function populateAttributeCodeWithDirective(
$extensibleEntityClass
) {
$attributeType = $directive[Converter::DATA_TYPE];
- $selectFields = $this->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]);
+ $selectFields = $this->joinProcessorHelper
+ ->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]);
+
foreach ($selectFields as $selectField) {
$internalAlias = $selectField[JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS];
if (isset($data[$internalAlias])) {
@@ -223,7 +179,7 @@ private function getJoinDirectivesForType($extensibleEntityClass)
$extensibleInterfaceName = $this->extensionAttributesFactory
->getExtensibleInterfaceName($extensibleEntityClass);
$extensibleInterfaceName = ltrim($extensibleInterfaceName, '\\');
- $config = $this->config->get();
+ $config = $this->joinProcessorHelper->getConfigData();
if (!isset($config[$extensibleInterfaceName])) {
return [];
}
diff --git a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php
new file mode 100644
index 0000000000000..0ac6f092e18ed
--- /dev/null
+++ b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php
@@ -0,0 +1,98 @@
+config = $config;
+ $this->joinDataInterfaceFactory = $joinDataInterfaceFactory;
+ }
+
+ /**
+ * Generate a list of select fields with mapping of client facing attribute names to field names used in SQL select.
+ *
+ * @param string $attributeCode
+ * @param array $selectFields
+ * @return array
+ */
+ public function getSelectFieldsMap($attributeCode, $selectFields)
+ {
+ $referenceTableAlias = $this->getReferenceTableAlias($attributeCode);
+ $useFieldInAlias = (count($selectFields) > 1);
+ $selectFieldsAliases = [];
+
+ foreach ($selectFields as $selectField) {
+ $internalFieldName = $selectField[Converter::JOIN_FIELD_COLUMN]
+ ? $selectField[Converter::JOIN_FIELD_COLUMN]
+ : $selectField[Converter::JOIN_FIELD];
+ $setterName = 'set'
+ . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($selectField[Converter::JOIN_FIELD]));
+ $selectFieldsAliases[] = [
+ JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS => $attributeCode
+ . ($useFieldInAlias ? '.' . $selectField[Converter::JOIN_FIELD] : ''),
+ JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS => $referenceTableAlias . '_' . $internalFieldName,
+ JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX => $referenceTableAlias . '.' . $internalFieldName,
+ JoinDataInterface::SELECT_FIELD_SETTER => $setterName
+ ];
+ }
+ return $selectFieldsAliases;
+ }
+
+ /**
+ * Generate reference table alias.
+ *
+ * @param string $attributeCode
+ * @return string
+ */
+ public function getReferenceTableAlias($attributeCode)
+ {
+ return 'extension_attribute_' . $attributeCode;
+ }
+
+ /**
+ * Returns config data values
+ *
+ * @return array|mixed|null
+ */
+ public function getConfigData()
+ {
+ return $this->config->get();
+ }
+
+ /**
+ * JoinDataInterface getter
+ *
+ * @return JoinDataInterface
+ */
+ public function getJoinDataInterface()
+ {
+ return $this->joinDataInterfaceFactory->create();
+ }
+}
diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php
index f3518c2fc3022..d47a76a4fe58c 100644
--- a/lib/internal/Magento/Framework/View/Asset/Source.php
+++ b/lib/internal/Magento/Framework/View/Asset/Source.php
@@ -122,7 +122,6 @@ public function getContent(LocalInterface $asset)
private function preProcess(LocalInterface $asset)
{
$sourceFile = $this->findSourceFile($asset);
- $dirCode = DirectoryList::ROOT;
$path = $this->rootDir->getRelativePath($sourceFile);
$chain = $this->createChain($asset, $path);