From fa1f103d6f7d8a6d05abd28a965af58b740cbb62 Mon Sep 17 00:00:00 2001 From: Andreas von Studnitz Date: Sat, 11 May 2019 20:59:40 +0200 Subject: [PATCH 01/70] Create new database table "expires at" --- app/code/Magento/User/etc/db_schema.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/User/etc/db_schema.xml b/app/code/Magento/User/etc/db_schema.xml index c3356a96b94a7..ddec6dd36eb84 100644 --- a/app/code/Magento/User/etc/db_schema.xml +++ b/app/code/Magento/User/etc/db_schema.xml @@ -36,7 +36,9 @@ default="0" comment="Failure Number"/> + comment="Expiration Lock Date"/> + From 451fae8970e40ecfa45ef40de25c6f0328dd9702 Mon Sep 17 00:00:00 2001 From: Andreas von Studnitz Date: Sat, 11 May 2019 20:59:40 +0200 Subject: [PATCH 02/70] 22833 Create new database table "expires at" --- app/code/Magento/User/etc/db_schema.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/User/etc/db_schema.xml b/app/code/Magento/User/etc/db_schema.xml index c3356a96b94a7..ddec6dd36eb84 100644 --- a/app/code/Magento/User/etc/db_schema.xml +++ b/app/code/Magento/User/etc/db_schema.xml @@ -36,7 +36,9 @@ default="0" comment="Failure Number"/> + comment="Expiration Lock Date"/> + From b17c54d7c5745edb4fe43587c6c342305a900720 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 11 May 2019 12:12:39 -0700 Subject: [PATCH 03/70] add whitelist for expires_at on user table (#22833) --- app/code/Magento/User/etc/db_schema_whitelist.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/User/etc/db_schema_whitelist.json b/app/code/Magento/User/etc/db_schema_whitelist.json index 2af77c0d8455b..3da364b803c5a 100644 --- a/app/code/Magento/User/etc/db_schema_whitelist.json +++ b/app/code/Magento/User/etc/db_schema_whitelist.json @@ -19,7 +19,8 @@ "interface_locale": true, "failures_num": true, "first_failure": true, - "lock_expires": true + "lock_expires": true, + "expires_at": true }, "constraint": { "PRIMARY": true, From c0c6825487004e67e1b4ad750e54d151c80c2cae Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 11 May 2019 13:41:30 -0700 Subject: [PATCH 04/70] add getters/setters for expires_at (#22833) --- app/code/Magento/User/Api/Data/UserInterface.php | 15 +++++++++++++++ app/code/Magento/User/Model/User.php | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/app/code/Magento/User/Api/Data/UserInterface.php b/app/code/Magento/User/Api/Data/UserInterface.php index 07bc190db5433..3faec1d10226f 100644 --- a/app/code/Magento/User/Api/Data/UserInterface.php +++ b/app/code/Magento/User/Api/Data/UserInterface.php @@ -162,4 +162,19 @@ public function getInterfaceLocale(); * @return $this */ public function setInterfaceLocale($interfaceLocale); + + /** + * Get user expiration date. + * + * @return string + */ + public function getExpiresAt(); + + /** + * Set user expiration date. + * + * @param string $expiresAt + * @return $this + */ + public function setExpiresAt($expiresAt); } diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index d8040b0bbaaac..68bd922d989bc 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -917,6 +917,22 @@ public function setInterfaceLocale($interfaceLocale) return $this->setData('interface_locale', $interfaceLocale); } + /** + * @inheritDoc + */ + public function getExpiresAt() + { + return $this->_getData('expires_at'); + } + + /** + * @inheritDoc + */ + public function setExpiresAt($expiresAt) + { + return $this->setData('expires_at', $expiresAt); + } + /** * Security check for admin user * From 6e2043ebcfde04a7184ef35dfeb4c3d976edabbf Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 11 May 2019 14:48:09 -0700 Subject: [PATCH 05/70] adding validation rules (#22833) --- .../Magento/User/Block/User/Edit/Tab/Main.php | 28 +++++++++++++++++++ app/code/Magento/User/Model/User.php | 4 +++ .../User/Model/UserValidationRules.php | 28 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/app/code/Magento/User/Block/User/Edit/Tab/Main.php b/app/code/Magento/User/Block/User/Edit/Tab/Main.php index 27e00483733d0..b69e50b7a6de7 100644 --- a/app/code/Magento/User/Block/User/Edit/Tab/Main.php +++ b/app/code/Magento/User/Block/User/Edit/Tab/Main.php @@ -170,6 +170,34 @@ protected function _prepareForm() ); } + $baseFieldset->addField( + 'expires_at', + 'date', + [ + 'name' => 'expires_at', + 'label' => __('Expiration Date'), + 'title' => __('Expiration Date'), + 'date_format' => 'yyyy-MM-dd', + 'time_format' => 'hh:mm:ss', + 'class' => 'validate-date', + ] + ) +// ->setAfterElementHtml("") + ; + $baseFieldset->addField('user_roles', 'hidden', ['name' => 'user_roles', 'id' => '_user_roles']); $currentUserVerificationFieldset = $form->addFieldset( diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 68bd922d989bc..3fdc35f8396a2 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -322,6 +322,10 @@ protected function _getValidationRulesBeforeSave() $this->validationRules->addPasswordConfirmationRule($validator, $this->getPasswordConfirmation()); } } + + if ($this->hasExpiresAt()) { + $this->validationRules->addExpiresAtRule($validator); + } return $validator; } diff --git a/app/code/Magento/User/Model/UserValidationRules.php b/app/code/Magento/User/Model/UserValidationRules.php index e40c785749e03..8539f78dce29d 100644 --- a/app/code/Magento/User/Model/UserValidationRules.php +++ b/app/code/Magento/User/Model/UserValidationRules.php @@ -125,4 +125,32 @@ public function addPasswordConfirmationRule( $validator->addRule($passwordConfirmation, 'password'); return $validator; } + + /** + * Adds validation rule for expiration date. + * @param \Magento\Framework\Validator\DataObject $validator + * @return \Magento\Framework\Validator\DataObject + */ + public function addExpiresAtRule(\Magento\Framework\Validator\DataObject $validator) + { + $expiresValidator = new \Zend_Validate_Date( + [ + 'format' => \Magento\Framework\Stdlib\DateTime::DATETIME_INTERNAL_FORMAT, + ] + ); + $expiresValidator->setMessage( + __('"Expiration date" invalid type entered.'), + \Zend_Validate_Date::INVALID + ); + $expiresValidator->setMessage( + __('"Expiration date" is not a valid date.'), + \Zend_Validate_Date::INVALID_DATE + ); + $expiresValidator->setMessage( + __('"Expiration date" does not fit the required date format.'), + \Zend_Validate_Date::FALSEFORMAT + ); + $validator->addRule($expiresValidator, 'expires_at'); + return $validator; + } } From 9136564ba2b72bdd2fa9c633a80be1edf43312a3 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 12 May 2019 11:18:48 -0700 Subject: [PATCH 06/70] finish validation; add tests (#22833) --- app/code/Magento/User/Model/User.php | 2 +- .../User/Model/UserValidationRules.php | 29 ++++++++++-- .../User/Model/Validator/ExpiresAt.php | 36 +++++++++++++++ .../Unit/Model/UserValidationRulesTest.php | 6 +++ .../Unit/Model/Validator/ExpiresAtTest.php | 46 +++++++++++++++++++ 5 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 app/code/Magento/User/Model/Validator/ExpiresAt.php create mode 100644 app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 3fdc35f8396a2..79f5ef7b3e3d3 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -323,7 +323,7 @@ protected function _getValidationRulesBeforeSave() } } - if ($this->hasExpiresAt()) { + if (!empty($this->getExpiresAt())) { $this->validationRules->addExpiresAtRule($validator); } return $validator; diff --git a/app/code/Magento/User/Model/UserValidationRules.php b/app/code/Magento/User/Model/UserValidationRules.php index 8539f78dce29d..7f48ae5dfd4f1 100644 --- a/app/code/Magento/User/Model/UserValidationRules.php +++ b/app/code/Magento/User/Model/UserValidationRules.php @@ -6,10 +6,12 @@ namespace Magento\User\Model; +use Magento\User\Model\Validator\ExpiresAt; use Magento\Framework\Validator\EmailAddress; use Magento\Framework\Validator\NotEmpty; use Magento\Framework\Validator\Regex; use Magento\Framework\Validator\StringLength; +use Magento\Framework\App\ObjectManager; /** * Class for adding validation rules to an Admin user @@ -23,6 +25,20 @@ class UserValidationRules * Minimum length of admin password */ const MIN_PASSWORD_LENGTH = 7; + /** + * @var Validator\ExpiresAt|null + */ + private $expiresValiator; + + /** + * UserValidationRules constructor. + * @param Validator\ExpiresAt|null $expiresValiator + */ + public function __construct(?ExpiresAt $expiresValiator = null) + { + $this->expiresValiator = $expiresValiator + ?: ObjectManager::getInstance()->get(ExpiresAt::class); + } /** * Adds validation rule for user first name, last name, username and email @@ -130,27 +146,30 @@ public function addPasswordConfirmationRule( * Adds validation rule for expiration date. * @param \Magento\Framework\Validator\DataObject $validator * @return \Magento\Framework\Validator\DataObject + * @throws \Zend_Validate_Exception */ public function addExpiresAtRule(\Magento\Framework\Validator\DataObject $validator) { - $expiresValidator = new \Zend_Validate_Date( + $dateValidator = new \Zend_Validate_Date( [ 'format' => \Magento\Framework\Stdlib\DateTime::DATETIME_INTERNAL_FORMAT, ] ); - $expiresValidator->setMessage( + $dateValidator->setMessage( __('"Expiration date" invalid type entered.'), \Zend_Validate_Date::INVALID ); - $expiresValidator->setMessage( + $dateValidator->setMessage( __('"Expiration date" is not a valid date.'), \Zend_Validate_Date::INVALID_DATE ); - $expiresValidator->setMessage( + $dateValidator->setMessage( __('"Expiration date" does not fit the required date format.'), \Zend_Validate_Date::FALSEFORMAT ); - $validator->addRule($expiresValidator, 'expires_at'); + $validator->addRule($dateValidator, 'expires_at'); + $validator->addRule($this->expiresValiator, 'expires_at'); + return $validator; } } diff --git a/app/code/Magento/User/Model/Validator/ExpiresAt.php b/app/code/Magento/User/Model/Validator/ExpiresAt.php new file mode 100644 index 0000000000000..531d5b2cc3607 --- /dev/null +++ b/app/code/Magento/User/Model/Validator/ExpiresAt.php @@ -0,0 +1,36 @@ +_addMessages([$message]); + } + + return !$this->hasMessages(); + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php index 5777e3b573890..9eb3ee203f0aa 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php @@ -42,4 +42,10 @@ public function testAddPasswordConfirmationRule() $this->validator->expects($this->once())->method('addRule')->willReturn($this->validator); $this->assertSame($this->validator, $this->rules->addPasswordConfirmationRule($this->validator, '')); } + + public function testAddExpiresAtRule() + { + $this->validator->expects($this->once())->method('addRule')->willReturn($this->validator); + $this->assertSame($this->validator, $this->rules->addExpiresAtRule($this->validator)); + } } diff --git a/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php b/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php new file mode 100644 index 0000000000000..0df6317a38cb3 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php @@ -0,0 +1,46 @@ +validator = $objectManager->getObject( + \Magento\User\Model\Validator\ExpiresAt::class + ); + } + + public function testIsValidWhenInvalid() + { + static::assertFalse($this->validator->isValid('2018-01-01 00:00:00')); + static::assertContains( + 'The expiration date must be later than the current date.', + $this->validator->getMessages() + ); + } + + public function testIsValidWhenValid() + { + $futureDate = new \DateTime(); + $futureDate->modify('+1 days'); + static::assertTrue($this->validator->isValid($futureDate->format('Y-m-d H:i:s'))); + static::assertEquals([], $this->validator->getMessages()); + } +} From 37c9d1200635c0bb6f4dd8c339cdc0bf11ee719a Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 12 May 2019 14:01:17 -0700 Subject: [PATCH 07/70] add cronjob, finish tests (#22833) --- .../Magento/User/Cron/DisableExpiredUsers.php | 45 +++++++++++++++ .../Model/ResourceModel/User/Collection.php | 19 ++++++- .../Unit/Model/UserValidationRulesTest.php | 2 +- app/code/Magento/User/etc/crontab.xml | 11 ++++ .../User/Cron/DisableExpiredUsersTest.php | 32 +++++++++++ .../ResourceModel/User/CollectionTest.php | 43 ++++++++++++++ .../testsuite/Magento/User/Model/UserTest.php | 11 ++++ .../Magento/User/_files/expired_users.php | 57 +++++++++++++++++++ 8 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/User/Cron/DisableExpiredUsers.php create mode 100644 app/code/Magento/User/etc/crontab.xml create mode 100644 dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php create mode 100644 dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/User/_files/expired_users.php diff --git a/app/code/Magento/User/Cron/DisableExpiredUsers.php b/app/code/Magento/User/Cron/DisableExpiredUsers.php new file mode 100644 index 0000000000000..08b35bd45433a --- /dev/null +++ b/app/code/Magento/User/Cron/DisableExpiredUsers.php @@ -0,0 +1,45 @@ +collectionFactory = $collectionFactory; + } + + /** + * Disable all expired user accounts. + */ + public function execute() + { + $users = $this->collectionFactory->create() + ->addExpiresAtFilter() + ->addFieldToFilter('is_active', 1) + ; + /** @var \Magento\User\Model\User $user */ + foreach ($users as $user) { + $user->setIsActive(0) + ->setExpiresAt(null) + ->save(); + } + } +} diff --git a/app/code/Magento/User/Model/ResourceModel/User/Collection.php b/app/code/Magento/User/Model/ResourceModel/User/Collection.php index 7683adae84365..133c253c28574 100644 --- a/app/code/Magento/User/Model/ResourceModel/User/Collection.php +++ b/app/code/Magento/User/Model/ResourceModel/User/Collection.php @@ -13,9 +13,9 @@ */ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { + /** * Define resource model - * * @return void */ protected function _construct() @@ -41,4 +41,21 @@ protected function _initSelect() ['role_name'] ); } + + /** + * Filter users by expires_at. + * @param string|null $now + * @return $this + */ + public function addExpiresAtFilter($now = null) + { + if ($now === null) { + $now = new \DateTime(); + $now->format('Y-m-d H:i:s'); + } + + $this->addFieldToFilter('expires_at', ['lt' => $now]); + + return $this; + } } diff --git a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php index 9eb3ee203f0aa..be23fde541eb2 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php @@ -45,7 +45,7 @@ public function testAddPasswordConfirmationRule() public function testAddExpiresAtRule() { - $this->validator->expects($this->once())->method('addRule')->willReturn($this->validator); + $this->validator->expects($this->atMost(2))->method('addRule')->willReturn($this->validator); $this->assertSame($this->validator, $this->rules->addExpiresAtRule($this->validator)); } } diff --git a/app/code/Magento/User/etc/crontab.xml b/app/code/Magento/User/etc/crontab.xml new file mode 100644 index 0000000000000..a58379f00e952 --- /dev/null +++ b/app/code/Magento/User/etc/crontab.xml @@ -0,0 +1,11 @@ + + + + + + 0 * * * * + + + + diff --git a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php new file mode 100644 index 0000000000000..2adac918ae29c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php @@ -0,0 +1,32 @@ +create(\Magento\User\Cron\DisableExpiredUsers::class); + $job->execute(); + + /** @var \Magento\User\Model\User $user */ + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername('adminUser3'); + static::assertEquals(0, $user->getIsActive()); + static::assertNull($user->getExpiresAt()); + } + +} diff --git a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php new file mode 100644 index 0000000000000..0a81814c982a8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php @@ -0,0 +1,43 @@ +collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\User\Model\ResourceModel\User\Collection::class + ); + } + + public function testSelectQueryInitialized() + { + static::assertContains( + 'main_table.user_id = user_role.user_id AND user_role.parent_id != 0', + $this->collection->getSelect()->__toString() + ); + } + + /** + * @magentoDataFixture Magento/User/_files/expired_users.php + */ + public function testExpiresAtFilter() + { + $this->collection->addExpiresAtFilter(); + static::assertCount(1, $this->collection); + } +} diff --git a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php index 8b85339afd789..eb5a3a0d11f53 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php @@ -387,6 +387,17 @@ public function testBeforeSavePasswordTooShort() $this->_model->save(); } + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The expiration date must be later than the current date. + * @magentoDbIsolation enabled + */ + public function testBeforeSaveExpireDateBeforeNow() + { + $this->_model->setExpiresAt('2010-01-01 00:00:00'); + $this->_model->save(); + } + /** * @dataProvider beforeSavePasswordInsecureDataProvider * @expectedException \Magento\Framework\Exception\LocalizedException diff --git a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php new file mode 100644 index 0000000000000..f6c562e4e03a5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php @@ -0,0 +1,57 @@ +create(\Magento\User\Model\User::class); +$model->setFirstname("John") + ->setLastname("Doe") + ->setUsername('adminUser3') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUser3@example.com') + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow'); +$model->save(); +$userIds[] = $model->getDataByKey('user_id'); + +/** @var $model \Magento\User\Model\User */ +$futureDate = new \DateTime(); +$futureDate->modify('+10 days'); +$model = $objectManager->create(\Magento\User\Model\User::class); +$model->setFirstname("John") + ->setLastname("Doe") + ->setUsername('adminUser4') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUser4@example.com') + ->setExpiresAt($futureDate->format('Y-m-d H:i:s')) + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow'); +$model->save(); +$userIds[] = $model->getDataByKey('user_id'); + +// need to bypass model validation to set expired date +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$conn = $resource->getConnection(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION); +$tableName = $resource->getTableName('admin_user'); +$sql = "UPDATE " . $tableName . " SET expires_at = '2010-01-01 00:00:00' WHERE user_id=" . + $userIds[0] . ";"; +$result = $conn->query($sql); + + From 8b5bec6685ffeeffeafaa0c097ca88209948b255 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 18 May 2019 14:12:58 -0400 Subject: [PATCH 08/70] update tests to check for active session (#22833) --- .../Magento/User/Cron/DisableExpiredUsers.php | 2 + .../User/Cron/DisableExpiredUsersTest.php | 50 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/User/Cron/DisableExpiredUsers.php b/app/code/Magento/User/Cron/DisableExpiredUsers.php index 08b35bd45433a..8e38814c565f8 100644 --- a/app/code/Magento/User/Cron/DisableExpiredUsers.php +++ b/app/code/Magento/User/Cron/DisableExpiredUsers.php @@ -28,6 +28,8 @@ public function __construct( /** * Disable all expired user accounts. + * TODO: add plugin to authentication to disable since not everyone + * has cron running (see \Magento\Security\Model\AdminSessionsManager::processLogin?) */ public function execute() { diff --git a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php index 2adac918ae29c..90afe70cf3307 100644 --- a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php @@ -13,20 +13,66 @@ */ class DisableExpiredUsersTest extends \PHPUnit\Framework\TestCase { + /** * @magentoDataFixture Magento/User/_files/expired_users.php */ - public function testExecute() + public function testExecuteWithExpiredUser() { + $adminUserNameFromFixture = 'adminUser3'; + + $tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Api\AdminTokenServiceInterface::class); + $tokenService->createAdminAccessToken( + $adminUserNameFromFixture, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + /** @var \Magento\User\Cron\DisableExpiredUsers $job */ $job = Bootstrap::getObjectManager()->create(\Magento\User\Cron\DisableExpiredUsers::class); $job->execute(); /** @var \Magento\User\Model\User $user */ $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername('adminUser3'); + $user->loadByUsername($adminUserNameFromFixture); + + /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ + $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); + $token = $tokenModel->loadByAdminId($user->getId()); + static::assertEquals(0, $user->getIsActive()); static::assertNull($user->getExpiresAt()); + static::assertEquals(null, $token->getId()); + } + + /** + * @magentoDataFixture Magento/User/_files/expired_users.php + */ + public function testExecuteWithNonExpiredUser() + { + $adminUserNameFromFixture = 'adminUser4'; + + $tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Api\AdminTokenServiceInterface::class); + $tokenService->createAdminAccessToken( + $adminUserNameFromFixture, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + + /** @var \Magento\User\Cron\DisableExpiredUsers $job */ + $job = Bootstrap::getObjectManager()->create(\Magento\User\Cron\DisableExpiredUsers::class); + $job->execute(); + + /** @var \Magento\User\Model\User $user */ + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + + /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ + $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); + $token = $tokenModel->loadByAdminId($user->getId()); + + static::assertEquals(1, $user->getIsActive()); + static::assertNotNull($user->getExpiresAt()); + static::assertNotNull($token->getId()); + } } From 0b78cbb272391b1d0e58cfef90eb0a4da3192daf Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 19 May 2019 16:49:49 -0400 Subject: [PATCH 09/70] Invalidate user sessions and handle expired users on login (#22833) --- .../Security/Model/AdminSessionsManager.php | 39 +++++++++++++- .../Magento/User/Cron/DisableExpiredUsers.php | 52 +++++++++++++------ .../Model/ResourceModel/User/Collection.php | 10 ++-- .../User/Cron/DisableExpiredUsersTest.php | 1 + .../ResourceModel/User/CollectionTest.php | 6 +-- 5 files changed, 84 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 7503fe04ba480..6dcc4226f5ac8 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -27,6 +27,11 @@ class AdminSessionsManager */ const LOGOUT_REASON_USER_LOCKED = 10; + /** + * User has been logged out due to an expired user account + */ + const LOGOUT_REASON_USER_EXPIRED = 11; + /** * @var ConfigInterface * @since 100.1.0 @@ -100,7 +105,7 @@ public function __construct( } /** - * Handle all others active sessions according Sharing Account Setting + * Handle all others active sessions according Sharing Account Setting and expired users. * * @return $this * @since 100.1.0 @@ -122,6 +127,11 @@ public function processLogin() } } + if ($this->authSession->getUser()->getExpiresAt()) + { + $this->revokeExpiredAdminUser(); + } + return $this; } @@ -144,6 +154,11 @@ public function processProlong() $this->getCurrentSession()->save(); } + if ($this->authSession->getUser()->getExpiresAt()) + { + $this->revokeExpiredAdminUser(); + } + return $this; } @@ -209,6 +224,11 @@ public function getLogoutReasonMessageByStatus($statusCode) 'Your account is temporarily disabled. Please try again later.' ); break; + case self::LOGOUT_REASON_USER_EXPIRED: + $reasonMessage = __( + 'Your account has expired.' + ); + break; default: $reasonMessage = __('Your current session has been expired.'); break; @@ -353,4 +373,21 @@ private function getIntervalBetweenConsecutiveProlongs() ) ); } + + /** + * Check if the current user is expired and, if so, revoke their admin token. + */ + private function revokeExpiredAdminUser() + { + $expiresAt = $this->dateTime->gmtTimestamp($this->authSession->getUser()->getExpiresAt()); + if ($expiresAt < $this->dateTime->gmtTimestamp()) { + $currentSessions = $this->getSessionsForCurrentUser(); + $currentSessions->setDataToAll('status', self::LOGOUT_REASON_USER_EXPIRED) + ->save(); + $this->authSession->getUser() + ->setIsActive(0) + ->setExpiresAt(null) + ->save(); + } + } } diff --git a/app/code/Magento/User/Cron/DisableExpiredUsers.php b/app/code/Magento/User/Cron/DisableExpiredUsers.php index 8e38814c565f8..312054dacd744 100644 --- a/app/code/Magento/User/Cron/DisableExpiredUsers.php +++ b/app/code/Magento/User/Cron/DisableExpiredUsers.php @@ -6,6 +6,8 @@ namespace Magento\User\Cron; +use Magento\Security\Model\AdminSessionsManager; + /** * Disable expired users. */ @@ -15,33 +17,53 @@ class DisableExpiredUsers /** * @var \Magento\User\Model\ResourceModel\User\CollectionFactory */ - private $collectionFactory; + private $userCollectionFactory; + /** + * @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory + */ + private $adminSessionCollectionFactory; + /** + * @var \Magento\Security\Model\ConfigInterface + */ + private $securityConfig; /** - * @param \Magento\User\Model\ResourceModel\User\CollectionFactory $collectionFactory + * @param \Magento\User\Model\ResourceModel\User\CollectionFactory $userCollectionFactory + * @param \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionCollectionFactory + * @param \Magento\Security\Model\ConfigInterface $securityConfig */ public function __construct( - \Magento\User\Model\ResourceModel\User\CollectionFactory $collectionFactory + \Magento\User\Model\ResourceModel\User\CollectionFactory $userCollectionFactory, + \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionCollectionFactory, + \Magento\Security\Model\ConfigInterface $securityConfig ) { - $this->collectionFactory = $collectionFactory; + $this->userCollectionFactory = $userCollectionFactory; + $this->adminSessionCollectionFactory = $adminSessionCollectionFactory; + $this->securityConfig = $securityConfig; } /** - * Disable all expired user accounts. - * TODO: add plugin to authentication to disable since not everyone - * has cron running (see \Magento\Security\Model\AdminSessionsManager::processLogin?) + * Disable all expired user accounts and invalidate their sessions. */ public function execute() { - $users = $this->collectionFactory->create() - ->addExpiresAtFilter() - ->addFieldToFilter('is_active', 1) - ; - /** @var \Magento\User\Model\User $user */ - foreach ($users as $user) { - $user->setIsActive(0) - ->setExpiresAt(null) + /** @var \Magento\User\Model\ResourceModel\User\Collection $users */ + $users = $this->userCollectionFactory->create() + ->addActiveExpiredUsersFilter(); + + if ($users->getSize() > 0) + { + /** @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection $currentSessions */ + $currentSessions = $this->adminSessionCollectionFactory->create() + ->addFieldToFilter('user_id', ['in' => $users->getAllIds()]) + ->addFieldToFilter('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_IN) + ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()); + $currentSessions->setDataToAll('status', AdminSessionsManager::LOGOUT_REASON_USER_EXPIRED) ->save(); } + + $users->setDataToAll('expires_at', null) + ->setDataToAll('is_active', 0) + ->save(); } } diff --git a/app/code/Magento/User/Model/ResourceModel/User/Collection.php b/app/code/Magento/User/Model/ResourceModel/User/Collection.php index 133c253c28574..6b258aa8000bd 100644 --- a/app/code/Magento/User/Model/ResourceModel/User/Collection.php +++ b/app/code/Magento/User/Model/ResourceModel/User/Collection.php @@ -43,18 +43,18 @@ protected function _initSelect() } /** - * Filter users by expires_at. - * @param string|null $now + * Filter for expired, active users. + * @param null $now * @return $this */ - public function addExpiresAtFilter($now = null) + public function addActiveExpiredUsersFilter($now = null) { if ($now === null) { $now = new \DateTime(); $now->format('Y-m-d H:i:s'); } - - $this->addFieldToFilter('expires_at', ['lt' => $now]); + $this->addFieldToFilter('expires_at', ['lt' => $now]) + ->addFieldToFilter('is_active', 1); return $this; } diff --git a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php index 90afe70cf3307..16909086c081f 100644 --- a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php @@ -9,6 +9,7 @@ use Magento\TestFramework\Helper\Bootstrap; /** + * TODO: test logging out sessions * @magentoAppArea adminhtml */ class DisableExpiredUsersTest extends \PHPUnit\Framework\TestCase diff --git a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php index 0a81814c982a8..262468b01fdaf 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php @@ -35,9 +35,9 @@ public function testSelectQueryInitialized() /** * @magentoDataFixture Magento/User/_files/expired_users.php */ - public function testExpiresAtFilter() + public function testExpiredActiveUsersFilter() { - $this->collection->addExpiresAtFilter(); - static::assertCount(1, $this->collection); + $this->collection->addActiveExpiredUsersFilter(); + static::assertGreaterThan(1, $this->collection->getSize()); } } From 976076fc6e37e0ee09d8ae00725c8a04f4c1823b Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 19 May 2019 17:02:27 -0400 Subject: [PATCH 10/70] Fix test for AdminSessionsManager calling getUser (#22833) --- .../Security/Test/Unit/Model/AdminSessionsManagerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index be0bdaecf8de3..d510ded0a2c39 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -151,7 +151,7 @@ public function testProcessLogin() ->method('getSessionId') ->willReturn($sessionId); - $this->authSessionMock->expects($this->once()) + $this->authSessionMock->expects($this->any()) ->method('getUser') ->willReturn($this->userMock); $this->userMock->expects($this->once()) From db95a0d0655939c7a26714e9540e8d6c361bd0ef Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 19 May 2019 18:04:40 -0400 Subject: [PATCH 11/70] handle calling getUser in test (#22833) --- app/code/Magento/Security/Model/AdminSessionsManager.php | 1 + .../Security/Test/Unit/Model/AdminSessionsManagerTest.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 6dcc4226f5ac8..500121802917d 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -154,6 +154,7 @@ public function processProlong() $this->getCurrentSession()->save(); } + // todo: don't necessarily have a user here if ($this->authSession->getUser()->getExpiresAt()) { $this->revokeExpiredAdminUser(); diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d510ded0a2c39..6a3a0d9f2632d 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -253,6 +253,10 @@ public function testProcessProlong() ->method('save') ->willReturnSelf(); + $this->authSessionMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->userMock); + $this->model->processProlong(); } From cf6633868766d246fac9552e45452f84d1832db5 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 19 May 2019 20:23:44 -0400 Subject: [PATCH 12/70] handle possibility of 1 or more in user collection test (#22833) --- .../Magento/User/Model/ResourceModel/User/CollectionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php index 262468b01fdaf..0c68027a36834 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php @@ -38,6 +38,6 @@ public function testSelectQueryInitialized() public function testExpiredActiveUsersFilter() { $this->collection->addActiveExpiredUsersFilter(); - static::assertGreaterThan(1, $this->collection->getSize()); + static::assertGreaterThanOrEqual(1, $this->collection->getSize()); } } From c552e5fe0f4e46d1d4ff0097ced8fcff46974b60 Mon Sep 17 00:00:00 2001 From: lfolco Date: Wed, 22 May 2019 18:48:45 -0400 Subject: [PATCH 13/70] clean up code style issues (#22833) --- app/code/Magento/Security/Model/AdminSessionsManager.php | 6 ++---- .../Security/Test/Unit/Model/AdminSessionsManagerTest.php | 6 ++++-- .../Magento/User/Model/ResourceModel/User/Collection.php | 4 +++- app/code/Magento/User/Model/UserValidationRules.php | 1 + app/code/Magento/User/Model/Validator/ExpiresAt.php | 1 + app/code/Magento/User/etc/crontab.xml | 6 ++++++ .../testsuite/Magento/User/Cron/DisableExpiredUsersTest.php | 2 -- .../testsuite/Magento/User/_files/expired_users.php | 2 -- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 500121802917d..dcf6b7c2365f3 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -127,8 +127,7 @@ public function processLogin() } } - if ($this->authSession->getUser()->getExpiresAt()) - { + if ($this->authSession->getUser()->getExpiresAt()) { $this->revokeExpiredAdminUser(); } @@ -155,8 +154,7 @@ public function processProlong() } // todo: don't necessarily have a user here - if ($this->authSession->getUser()->getExpiresAt()) - { + if ($this->authSession->getUser()->getExpiresAt()) { $this->revokeExpiredAdminUser(); } diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index 6a3a0d9f2632d..d729fe78f0f57 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -91,7 +91,8 @@ public function setUp() ['create'] ); - $this->currentSessionMock = $this->createPartialMock(\Magento\Security\Model\AdminSessionInfo::class, [ + $this->currentSessionMock = $this->createPartialMock( + \Magento\Security\Model\AdminSessionInfo::class, [ 'isActive', 'getStatus', 'load', @@ -101,7 +102,8 @@ public function setUp() 'getUserId', 'getSessionId', 'getUpdatedAt' - ]); + ] + ); $this->securityConfigMock = $this->getMockBuilder(\Magento\Security\Model\ConfigInterface::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/User/Model/ResourceModel/User/Collection.php b/app/code/Magento/User/Model/ResourceModel/User/Collection.php index 6b258aa8000bd..f1fd45e73c74c 100644 --- a/app/code/Magento/User/Model/ResourceModel/User/Collection.php +++ b/app/code/Magento/User/Model/ResourceModel/User/Collection.php @@ -16,6 +16,7 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab /** * Define resource model + * * @return void */ protected function _construct() @@ -44,7 +45,8 @@ protected function _initSelect() /** * Filter for expired, active users. - * @param null $now + * + * @param string $now * @return $this */ public function addActiveExpiredUsersFilter($now = null) diff --git a/app/code/Magento/User/Model/UserValidationRules.php b/app/code/Magento/User/Model/UserValidationRules.php index 7f48ae5dfd4f1..49bc75e5ae8b4 100644 --- a/app/code/Magento/User/Model/UserValidationRules.php +++ b/app/code/Magento/User/Model/UserValidationRules.php @@ -144,6 +144,7 @@ public function addPasswordConfirmationRule( /** * Adds validation rule for expiration date. + * * @param \Magento\Framework\Validator\DataObject $validator * @return \Magento\Framework\Validator\DataObject * @throws \Zend_Validate_Exception diff --git a/app/code/Magento/User/Model/Validator/ExpiresAt.php b/app/code/Magento/User/Model/Validator/ExpiresAt.php index 531d5b2cc3607..09ff78ae53063 100644 --- a/app/code/Magento/User/Model/Validator/ExpiresAt.php +++ b/app/code/Magento/User/Model/Validator/ExpiresAt.php @@ -17,6 +17,7 @@ class ExpiresAt extends AbstractValidator /** * Ensure that the given date is later than the current date. + * * @param String $value * @return bool * @throws \Exception diff --git a/app/code/Magento/User/etc/crontab.xml b/app/code/Magento/User/etc/crontab.xml index a58379f00e952..6919856857b40 100644 --- a/app/code/Magento/User/etc/crontab.xml +++ b/app/code/Magento/User/etc/crontab.xml @@ -1,4 +1,10 @@ + diff --git a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php index 16909086c081f..c532dc1aa9c94 100644 --- a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php @@ -73,7 +73,5 @@ public function testExecuteWithNonExpiredUser() static::assertEquals(1, $user->getIsActive()); static::assertNotNull($user->getExpiresAt()); static::assertNotNull($token->getId()); - } - } diff --git a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php index f6c562e4e03a5..f11797156031c 100644 --- a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php +++ b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php @@ -53,5 +53,3 @@ $sql = "UPDATE " . $tableName . " SET expires_at = '2010-01-01 00:00:00' WHERE user_id=" . $userIds[0] . ";"; $result = $conn->query($sql); - - From d5855baf0b23dc80b4615b6b3e4b4db491bb0177 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 1 Jun 2019 13:25:11 -0400 Subject: [PATCH 14/70] clean up code style issues (#22833) --- app/code/Magento/User/Cron/DisableExpiredUsers.php | 3 +-- .../testsuite/Magento/User/_files/expired_users.php | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/User/Cron/DisableExpiredUsers.php b/app/code/Magento/User/Cron/DisableExpiredUsers.php index 312054dacd744..07a79d08733af 100644 --- a/app/code/Magento/User/Cron/DisableExpiredUsers.php +++ b/app/code/Magento/User/Cron/DisableExpiredUsers.php @@ -51,8 +51,7 @@ public function execute() $users = $this->userCollectionFactory->create() ->addActiveExpiredUsersFilter(); - if ($users->getSize() > 0) - { + if ($users->getSize() > 0) { /** @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection $currentSessions */ $currentSessions = $this->adminSessionCollectionFactory->create() ->addFieldToFilter('user_id', ['in' => $users->getAllIds()]) diff --git a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php index f11797156031c..c35a4ac66b9cc 100644 --- a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php +++ b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php @@ -13,9 +13,9 @@ /** @var $model \Magento\User\Model\User */ $model = $objectManager->create(\Magento\User\Model\User::class); -$model->setFirstname("John") - ->setLastname("Doe") - ->setUsername('adminUser3') +$model->setFirstName("John") + ->setLastName("Doe") + ->setUserName('adminUser3') ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) ->setEmail('adminUser3@example.com') ->setRoleType('G') @@ -31,9 +31,9 @@ $futureDate = new \DateTime(); $futureDate->modify('+10 days'); $model = $objectManager->create(\Magento\User\Model\User::class); -$model->setFirstname("John") - ->setLastname("Doe") - ->setUsername('adminUser4') +$model->setFirstName("John") + ->setLastName("Doe") + ->setUserName('adminUser4') ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) ->setEmail('adminUser4@example.com') ->setExpiresAt($futureDate->format('Y-m-d H:i:s')) From 439f56576a772764f3f88ee112348bc92ad27cea Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 1 Jun 2019 17:23:37 -0400 Subject: [PATCH 15/70] clean up code style issues (#22833) --- .../Security/Model/AdminSessionsManager.php | 5 +++-- .../Unit/Model/AdminSessionsManagerTest.php | 3 ++- .../Magento/User/Block/User/Edit/Tab/Main.php | 17 +---------------- .../Magento/User/Model/Validator/ExpiresAt.php | 1 + .../Test/Unit/Model/UserValidationRulesTest.php | 5 +++++ 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index dcf6b7c2365f3..2a9024c54ee95 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -324,6 +324,8 @@ protected function createNewSession() } /** + * Create admin session info collection. + * * @return \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection * @since 100.1.0 */ @@ -334,8 +336,7 @@ protected function createAdminSessionInfoCollection() /** * Calculates diff between now and last session updated_at - * and decides whether new prolong must be triggered or not - * + * and decides whether new prolong must be triggered or not. * This is done to limit amount of session prolongs and updates to database * within some period of time - X * X - is calculated in getIntervalBetweenConsecutiveProlongs() diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d729fe78f0f57..d7495cd9ce173 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -92,7 +92,8 @@ public function setUp() ); $this->currentSessionMock = $this->createPartialMock( - \Magento\Security\Model\AdminSessionInfo::class, [ + \Magento\Security\Model\AdminSessionInfo::class, + [ 'isActive', 'getStatus', 'load', diff --git a/app/code/Magento/User/Block/User/Edit/Tab/Main.php b/app/code/Magento/User/Block/User/Edit/Tab/Main.php index b69e50b7a6de7..9cdec228eb382 100644 --- a/app/code/Magento/User/Block/User/Edit/Tab/Main.php +++ b/app/code/Magento/User/Block/User/Edit/Tab/Main.php @@ -181,22 +181,7 @@ protected function _prepareForm() 'time_format' => 'hh:mm:ss', 'class' => 'validate-date', ] - ) -// ->setAfterElementHtml("") - ; + ); $baseFieldset->addField('user_roles', 'hidden', ['name' => 'user_roles', 'id' => '_user_roles']); diff --git a/app/code/Magento/User/Model/Validator/ExpiresAt.php b/app/code/Magento/User/Model/Validator/ExpiresAt.php index 09ff78ae53063..1aeba4b3e1aaf 100644 --- a/app/code/Magento/User/Model/Validator/ExpiresAt.php +++ b/app/code/Magento/User/Model/Validator/ExpiresAt.php @@ -10,6 +10,7 @@ /** * Class ExpiresAt + * * @package Magento\User\Model\Validator */ class ExpiresAt extends AbstractValidator diff --git a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php index be23fde541eb2..146476cd25e42 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php @@ -7,6 +7,11 @@ use Magento\User\Model\UserValidationRules; +/** + * Class UserValidationRulesTest + * + * @package Magento\User\Test\Unit\Model + */ class UserValidationRulesTest extends \PHPUnit\Framework\TestCase { /** From ae08dada13b265a34f7919d3f374ce55e21385eb Mon Sep 17 00:00:00 2001 From: lfolco Date: Fri, 21 Jun 2019 12:14:48 -0400 Subject: [PATCH 16/70] move changes out of user and into security (magento/magento2#22833: Short-term admin accounts) --- .../Magento/User/Api/Data/UserInterface.php | 15 ---- .../Magento/User/Block/User/Edit/Tab/Main.php | 2 +- .../Magento/User/Cron/DisableExpiredUsers.php | 68 ------------------- .../Model/ResourceModel/User/Collection.php | 20 +----- app/code/Magento/User/Model/User.php | 44 ++---------- .../User/Model/UserValidationRules.php | 48 ------------- .../Unit/Model/UserValidationRulesTest.php | 11 --- app/code/Magento/User/etc/crontab.xml | 17 ----- app/code/Magento/User/etc/db_schema.xml | 4 +- .../Magento/User/etc/db_schema_whitelist.json | 5 +- .../ResourceModel/User/CollectionTest.php | 43 ------------ .../testsuite/Magento/User/Model/UserTest.php | 11 --- 12 files changed, 10 insertions(+), 278 deletions(-) delete mode 100644 app/code/Magento/User/Cron/DisableExpiredUsers.php delete mode 100644 app/code/Magento/User/etc/crontab.xml delete mode 100644 dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php diff --git a/app/code/Magento/User/Api/Data/UserInterface.php b/app/code/Magento/User/Api/Data/UserInterface.php index 3faec1d10226f..07bc190db5433 100644 --- a/app/code/Magento/User/Api/Data/UserInterface.php +++ b/app/code/Magento/User/Api/Data/UserInterface.php @@ -162,19 +162,4 @@ public function getInterfaceLocale(); * @return $this */ public function setInterfaceLocale($interfaceLocale); - - /** - * Get user expiration date. - * - * @return string - */ - public function getExpiresAt(); - - /** - * Set user expiration date. - * - * @param string $expiresAt - * @return $this - */ - public function setExpiresAt($expiresAt); } diff --git a/app/code/Magento/User/Block/User/Edit/Tab/Main.php b/app/code/Magento/User/Block/User/Edit/Tab/Main.php index 9cdec228eb382..85ac6898a27c3 100644 --- a/app/code/Magento/User/Block/User/Edit/Tab/Main.php +++ b/app/code/Magento/User/Block/User/Edit/Tab/Main.php @@ -169,7 +169,7 @@ protected function _prepareForm() ] ); } - + // TODO: use store time and convert to GMT $baseFieldset->addField( 'expires_at', 'date', diff --git a/app/code/Magento/User/Cron/DisableExpiredUsers.php b/app/code/Magento/User/Cron/DisableExpiredUsers.php deleted file mode 100644 index 07a79d08733af..0000000000000 --- a/app/code/Magento/User/Cron/DisableExpiredUsers.php +++ /dev/null @@ -1,68 +0,0 @@ -userCollectionFactory = $userCollectionFactory; - $this->adminSessionCollectionFactory = $adminSessionCollectionFactory; - $this->securityConfig = $securityConfig; - } - - /** - * Disable all expired user accounts and invalidate their sessions. - */ - public function execute() - { - /** @var \Magento\User\Model\ResourceModel\User\Collection $users */ - $users = $this->userCollectionFactory->create() - ->addActiveExpiredUsersFilter(); - - if ($users->getSize() > 0) { - /** @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection $currentSessions */ - $currentSessions = $this->adminSessionCollectionFactory->create() - ->addFieldToFilter('user_id', ['in' => $users->getAllIds()]) - ->addFieldToFilter('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_IN) - ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()); - $currentSessions->setDataToAll('status', AdminSessionsManager::LOGOUT_REASON_USER_EXPIRED) - ->save(); - } - - $users->setDataToAll('expires_at', null) - ->setDataToAll('is_active', 0) - ->save(); - } -} diff --git a/app/code/Magento/User/Model/ResourceModel/User/Collection.php b/app/code/Magento/User/Model/ResourceModel/User/Collection.php index f1fd45e73c74c..422afb47f0a0e 100644 --- a/app/code/Magento/User/Model/ResourceModel/User/Collection.php +++ b/app/code/Magento/User/Model/ResourceModel/User/Collection.php @@ -13,7 +13,6 @@ */ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { - /** * Define resource model * @@ -28,6 +27,7 @@ protected function _construct() * Collection Init Select * * @return $this + * @since 101.1.0 */ protected function _initSelect() { @@ -42,22 +42,4 @@ protected function _initSelect() ['role_name'] ); } - - /** - * Filter for expired, active users. - * - * @param string $now - * @return $this - */ - public function addActiveExpiredUsersFilter($now = null) - { - if ($now === null) { - $now = new \DateTime(); - $now->format('Y-m-d H:i:s'); - } - $this->addFieldToFilter('expires_at', ['lt' => $now]) - ->addFieldToFilter('is_active', 1); - - return $this; - } } diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 79f5ef7b3e3d3..c734096ca2014 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -115,12 +115,12 @@ class User extends AbstractModel implements StorageInterface, UserInterface protected $_encryptor; /** - * @deprecated + * @deprecated 101.1.0 */ protected $_transportBuilder; /** - * @deprecated + * @deprecated 101.1.0 */ protected $_storeManager; @@ -140,7 +140,7 @@ class User extends AbstractModel implements StorageInterface, UserInterface private $notificator; /** - * @deprecated + * @deprecated 101.1.0 */ private $deploymentConfig; @@ -212,14 +212,9 @@ protected function _construct() * Removing dependencies and leaving only entity's properties. * * @return string[] - * - * @SuppressWarnings(PHPMD.SerializationAware) - * @deprecated Do not use PHP serialization. */ public function __sleep() { - trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); - $properties = parent::__sleep(); return array_diff( $properties, @@ -245,14 +240,9 @@ public function __sleep() * Restoring required objects after serialization. * * @return void - * - * @SuppressWarnings(PHPMD.SerializationAware) - * @deprecated Do not use PHP serialization. */ public function __wakeup() { - trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); - parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->serializer = $objectManager->get(Json::class); @@ -322,10 +312,6 @@ protected function _getValidationRulesBeforeSave() $this->validationRules->addPasswordConfirmationRule($validator, $this->getPasswordConfirmation()); } } - - if (!empty($this->getExpiresAt())) { - $this->validationRules->addExpiresAtRule($validator); - } return $validator; } @@ -420,10 +406,6 @@ public function getRoles() */ public function getRole() { - if ($this->getData('extracted_role')) { - $this->_role = $this->getData('extracted_role'); - $this->unsetData('extracted_role'); - } if (null === $this->_role) { $this->_role = $this->_roleFactory->create(); $roles = $this->getRoles(); @@ -459,7 +441,7 @@ public function roleUserExists() /** * Send email with reset password confirmation link. * - * @deprecated + * @deprecated 101.1.0 * @see NotificatorInterface::sendForgotPassword() * * @return $this @@ -539,7 +521,7 @@ protected function createChangesDescriptionString() * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 - * @deprecated + * @deprecated 101.1.0 * @see NotificatorInterface::sendUpdated() * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -921,22 +903,6 @@ public function setInterfaceLocale($interfaceLocale) return $this->setData('interface_locale', $interfaceLocale); } - /** - * @inheritDoc - */ - public function getExpiresAt() - { - return $this->_getData('expires_at'); - } - - /** - * @inheritDoc - */ - public function setExpiresAt($expiresAt) - { - return $this->setData('expires_at', $expiresAt); - } - /** * Security check for admin user * diff --git a/app/code/Magento/User/Model/UserValidationRules.php b/app/code/Magento/User/Model/UserValidationRules.php index 49bc75e5ae8b4..e40c785749e03 100644 --- a/app/code/Magento/User/Model/UserValidationRules.php +++ b/app/code/Magento/User/Model/UserValidationRules.php @@ -6,12 +6,10 @@ namespace Magento\User\Model; -use Magento\User\Model\Validator\ExpiresAt; use Magento\Framework\Validator\EmailAddress; use Magento\Framework\Validator\NotEmpty; use Magento\Framework\Validator\Regex; use Magento\Framework\Validator\StringLength; -use Magento\Framework\App\ObjectManager; /** * Class for adding validation rules to an Admin user @@ -25,20 +23,6 @@ class UserValidationRules * Minimum length of admin password */ const MIN_PASSWORD_LENGTH = 7; - /** - * @var Validator\ExpiresAt|null - */ - private $expiresValiator; - - /** - * UserValidationRules constructor. - * @param Validator\ExpiresAt|null $expiresValiator - */ - public function __construct(?ExpiresAt $expiresValiator = null) - { - $this->expiresValiator = $expiresValiator - ?: ObjectManager::getInstance()->get(ExpiresAt::class); - } /** * Adds validation rule for user first name, last name, username and email @@ -141,36 +125,4 @@ public function addPasswordConfirmationRule( $validator->addRule($passwordConfirmation, 'password'); return $validator; } - - /** - * Adds validation rule for expiration date. - * - * @param \Magento\Framework\Validator\DataObject $validator - * @return \Magento\Framework\Validator\DataObject - * @throws \Zend_Validate_Exception - */ - public function addExpiresAtRule(\Magento\Framework\Validator\DataObject $validator) - { - $dateValidator = new \Zend_Validate_Date( - [ - 'format' => \Magento\Framework\Stdlib\DateTime::DATETIME_INTERNAL_FORMAT, - ] - ); - $dateValidator->setMessage( - __('"Expiration date" invalid type entered.'), - \Zend_Validate_Date::INVALID - ); - $dateValidator->setMessage( - __('"Expiration date" is not a valid date.'), - \Zend_Validate_Date::INVALID_DATE - ); - $dateValidator->setMessage( - __('"Expiration date" does not fit the required date format.'), - \Zend_Validate_Date::FALSEFORMAT - ); - $validator->addRule($dateValidator, 'expires_at'); - $validator->addRule($this->expiresValiator, 'expires_at'); - - return $validator; - } } diff --git a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php index 146476cd25e42..5777e3b573890 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserValidationRulesTest.php @@ -7,11 +7,6 @@ use Magento\User\Model\UserValidationRules; -/** - * Class UserValidationRulesTest - * - * @package Magento\User\Test\Unit\Model - */ class UserValidationRulesTest extends \PHPUnit\Framework\TestCase { /** @@ -47,10 +42,4 @@ public function testAddPasswordConfirmationRule() $this->validator->expects($this->once())->method('addRule')->willReturn($this->validator); $this->assertSame($this->validator, $this->rules->addPasswordConfirmationRule($this->validator, '')); } - - public function testAddExpiresAtRule() - { - $this->validator->expects($this->atMost(2))->method('addRule')->willReturn($this->validator); - $this->assertSame($this->validator, $this->rules->addExpiresAtRule($this->validator)); - } } diff --git a/app/code/Magento/User/etc/crontab.xml b/app/code/Magento/User/etc/crontab.xml deleted file mode 100644 index 6919856857b40..0000000000000 --- a/app/code/Magento/User/etc/crontab.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - 0 * * * * - - - - diff --git a/app/code/Magento/User/etc/db_schema.xml b/app/code/Magento/User/etc/db_schema.xml index ddec6dd36eb84..c3356a96b94a7 100644 --- a/app/code/Magento/User/etc/db_schema.xml +++ b/app/code/Magento/User/etc/db_schema.xml @@ -36,9 +36,7 @@ default="0" comment="Failure Number"/> - + comment="Expiration Lock Dates"/> diff --git a/app/code/Magento/User/etc/db_schema_whitelist.json b/app/code/Magento/User/etc/db_schema_whitelist.json index 3da364b803c5a..c214cae477cd7 100644 --- a/app/code/Magento/User/etc/db_schema_whitelist.json +++ b/app/code/Magento/User/etc/db_schema_whitelist.json @@ -19,8 +19,7 @@ "interface_locale": true, "failures_num": true, "first_failure": true, - "lock_expires": true, - "expires_at": true + "lock_expires": true }, "constraint": { "PRIMARY": true, @@ -43,4 +42,4 @@ "ADMIN_PASSWORDS_USER_ID_ADMIN_USER_USER_ID": true } } -} \ No newline at end of file +} diff --git a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php deleted file mode 100644 index 0c68027a36834..0000000000000 --- a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/User/CollectionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\User\Model\ResourceModel\User\Collection::class - ); - } - - public function testSelectQueryInitialized() - { - static::assertContains( - 'main_table.user_id = user_role.user_id AND user_role.parent_id != 0', - $this->collection->getSelect()->__toString() - ); - } - - /** - * @magentoDataFixture Magento/User/_files/expired_users.php - */ - public function testExpiredActiveUsersFilter() - { - $this->collection->addActiveExpiredUsersFilter(); - static::assertGreaterThanOrEqual(1, $this->collection->getSize()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php index eb5a3a0d11f53..8b85339afd789 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php @@ -387,17 +387,6 @@ public function testBeforeSavePasswordTooShort() $this->_model->save(); } - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage The expiration date must be later than the current date. - * @magentoDbIsolation enabled - */ - public function testBeforeSaveExpireDateBeforeNow() - { - $this->_model->setExpiresAt('2010-01-01 00:00:00'); - $this->_model->save(); - } - /** * @dataProvider beforeSavePasswordInsecureDataProvider * @expectedException \Magento\Framework\Exception\LocalizedException From ad4628b981a5a7afd529226178a87d2f9c518576 Mon Sep 17 00:00:00 2001 From: lfolco Date: Fri, 21 Jun 2019 12:34:17 -0400 Subject: [PATCH 17/70] move changes out of user and into security (magento/magento2#22833: Short-termadmin accounts) --- .../User/Model/Validator/ExpiresAt.php | 38 --------------- .../Unit/Model/Validator/ExpiresAtTest.php | 46 ------------------- 2 files changed, 84 deletions(-) delete mode 100644 app/code/Magento/User/Model/Validator/ExpiresAt.php delete mode 100644 app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php diff --git a/app/code/Magento/User/Model/Validator/ExpiresAt.php b/app/code/Magento/User/Model/Validator/ExpiresAt.php deleted file mode 100644 index 1aeba4b3e1aaf..0000000000000 --- a/app/code/Magento/User/Model/Validator/ExpiresAt.php +++ /dev/null @@ -1,38 +0,0 @@ -_addMessages([$message]); - } - - return !$this->hasMessages(); - } -} diff --git a/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php b/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php deleted file mode 100644 index 0df6317a38cb3..0000000000000 --- a/app/code/Magento/User/Test/Unit/Model/Validator/ExpiresAtTest.php +++ /dev/null @@ -1,46 +0,0 @@ -validator = $objectManager->getObject( - \Magento\User\Model\Validator\ExpiresAt::class - ); - } - - public function testIsValidWhenInvalid() - { - static::assertFalse($this->validator->isValid('2018-01-01 00:00:00')); - static::assertContains( - 'The expiration date must be later than the current date.', - $this->validator->getMessages() - ); - } - - public function testIsValidWhenValid() - { - $futureDate = new \DateTime(); - $futureDate->modify('+1 days'); - static::assertTrue($this->validator->isValid($futureDate->format('Y-m-d H:i:s'))); - static::assertEquals([], $this->validator->getMessages()); - } -} From 3c608fd284e485f81f45f0a27563bc6e7b663937 Mon Sep 17 00:00:00 2001 From: lfolco Date: Fri, 21 Jun 2019 12:34:39 -0400 Subject: [PATCH 18/70] move changes out of user and into security (magento/magento2#22833: Short-termadmin accounts) --- .../User/Cron/DisableExpiredUsersTest.php | 77 ------------------- .../Magento/User/_files/expired_users.php | 55 ------------- 2 files changed, 132 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/User/_files/expired_users.php diff --git a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php b/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php deleted file mode 100644 index c532dc1aa9c94..0000000000000 --- a/dev/tests/integration/testsuite/Magento/User/Cron/DisableExpiredUsersTest.php +++ /dev/null @@ -1,77 +0,0 @@ -get(\Magento\Integration\Api\AdminTokenServiceInterface::class); - $tokenService->createAdminAccessToken( - $adminUserNameFromFixture, - \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD - ); - - /** @var \Magento\User\Cron\DisableExpiredUsers $job */ - $job = Bootstrap::getObjectManager()->create(\Magento\User\Cron\DisableExpiredUsers::class); - $job->execute(); - - /** @var \Magento\User\Model\User $user */ - $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername($adminUserNameFromFixture); - - /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ - $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); - $token = $tokenModel->loadByAdminId($user->getId()); - - static::assertEquals(0, $user->getIsActive()); - static::assertNull($user->getExpiresAt()); - static::assertEquals(null, $token->getId()); - } - - /** - * @magentoDataFixture Magento/User/_files/expired_users.php - */ - public function testExecuteWithNonExpiredUser() - { - $adminUserNameFromFixture = 'adminUser4'; - - $tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Api\AdminTokenServiceInterface::class); - $tokenService->createAdminAccessToken( - $adminUserNameFromFixture, - \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD - ); - - /** @var \Magento\User\Cron\DisableExpiredUsers $job */ - $job = Bootstrap::getObjectManager()->create(\Magento\User\Cron\DisableExpiredUsers::class); - $job->execute(); - - /** @var \Magento\User\Model\User $user */ - $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername($adminUserNameFromFixture); - - /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ - $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); - $token = $tokenModel->loadByAdminId($user->getId()); - - static::assertEquals(1, $user->getIsActive()); - static::assertNotNull($user->getExpiresAt()); - static::assertNotNull($token->getId()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php deleted file mode 100644 index c35a4ac66b9cc..0000000000000 --- a/dev/tests/integration/testsuite/Magento/User/_files/expired_users.php +++ /dev/null @@ -1,55 +0,0 @@ -create(\Magento\User\Model\User::class); -$model->setFirstName("John") - ->setLastName("Doe") - ->setUserName('adminUser3') - ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) - ->setEmail('adminUser3@example.com') - ->setRoleType('G') - ->setResourceId('Magento_Adminhtml::all') - ->setPrivileges("") - ->setAssertId(0) - ->setRoleId(1) - ->setPermission('allow'); -$model->save(); -$userIds[] = $model->getDataByKey('user_id'); - -/** @var $model \Magento\User\Model\User */ -$futureDate = new \DateTime(); -$futureDate->modify('+10 days'); -$model = $objectManager->create(\Magento\User\Model\User::class); -$model->setFirstName("John") - ->setLastName("Doe") - ->setUserName('adminUser4') - ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) - ->setEmail('adminUser4@example.com') - ->setExpiresAt($futureDate->format('Y-m-d H:i:s')) - ->setRoleType('G') - ->setResourceId('Magento_Adminhtml::all') - ->setPrivileges("") - ->setAssertId(0) - ->setRoleId(1) - ->setPermission('allow'); -$model->save(); -$userIds[] = $model->getDataByKey('user_id'); - -// need to bypass model validation to set expired date -$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); -$conn = $resource->getConnection(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION); -$tableName = $resource->getTableName('admin_user'); -$sql = "UPDATE " . $tableName . " SET expires_at = '2010-01-01 00:00:00' WHERE user_id=" . - $userIds[0] . ";"; -$result = $conn->query($sql); From e4ec0e26e1e91d31cd2b391835ee86c6815bd556 Mon Sep 17 00:00:00 2001 From: lfolco Date: Fri, 21 Jun 2019 12:42:03 -0400 Subject: [PATCH 19/70] revert merge changes (magento/magento2#22833: Short-term admin accounts) --- .../Model/ResourceModel/User/Collection.php | 1 - app/code/Magento/User/Model/User.php | 24 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/User/Model/ResourceModel/User/Collection.php b/app/code/Magento/User/Model/ResourceModel/User/Collection.php index 422afb47f0a0e..7683adae84365 100644 --- a/app/code/Magento/User/Model/ResourceModel/User/Collection.php +++ b/app/code/Magento/User/Model/ResourceModel/User/Collection.php @@ -27,7 +27,6 @@ protected function _construct() * Collection Init Select * * @return $this - * @since 101.1.0 */ protected function _initSelect() { diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index c734096ca2014..d8040b0bbaaac 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -115,12 +115,12 @@ class User extends AbstractModel implements StorageInterface, UserInterface protected $_encryptor; /** - * @deprecated 101.1.0 + * @deprecated */ protected $_transportBuilder; /** - * @deprecated 101.1.0 + * @deprecated */ protected $_storeManager; @@ -140,7 +140,7 @@ class User extends AbstractModel implements StorageInterface, UserInterface private $notificator; /** - * @deprecated 101.1.0 + * @deprecated */ private $deploymentConfig; @@ -212,9 +212,14 @@ protected function _construct() * Removing dependencies and leaving only entity's properties. * * @return string[] + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); return array_diff( $properties, @@ -240,9 +245,14 @@ public function __sleep() * Restoring required objects after serialization. * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->serializer = $objectManager->get(Json::class); @@ -406,6 +416,10 @@ public function getRoles() */ public function getRole() { + if ($this->getData('extracted_role')) { + $this->_role = $this->getData('extracted_role'); + $this->unsetData('extracted_role'); + } if (null === $this->_role) { $this->_role = $this->_roleFactory->create(); $roles = $this->getRoles(); @@ -441,7 +455,7 @@ public function roleUserExists() /** * Send email with reset password confirmation link. * - * @deprecated 101.1.0 + * @deprecated * @see NotificatorInterface::sendForgotPassword() * * @return $this @@ -521,7 +535,7 @@ protected function createChangesDescriptionString() * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 - * @deprecated 101.1.0 + * @deprecated * @see NotificatorInterface::sendUpdated() * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ From e51bf0a909ab69ff397dac26a8edcf34e88b3008 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 30 Jun 2019 12:55:13 -0400 Subject: [PATCH 20/70] Create models for userexpiration, add tests and events (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/AdminSessionsManager.php | 75 ++++---- .../Model/Plugin/UserValidationRules.php | 40 ++++ .../Model/ResourceModel/UserExpiration.php | 49 +++++ .../UserExpiration/Collection.php | 75 ++++++++ .../Magento/Security/Model/UserExpiration.php | 62 ++++++ .../Model/UserExpiration/Validator.php | 45 +++++ .../Security/Model/UserExpirationManager.php | 179 ++++++++++++++++++ .../Security/Observer/AfterAdminUserLoad.php | 54 ++++++ .../Security/Observer/AfterAdminUserSave.php | 65 +++++++ .../Observer/BeforeAdminUserAuthenticate.php | 61 ++++++ .../Unit/Model/AdminSessionsManagerTest.php | 2 +- .../Model/Plugin/UserValidationRulesTest.php | 63 ++++++ .../Model/UserExpiration/ValidatorTest.php | 49 +++++ .../Unit/Observer/AfterAdminUserLoadTest.php | 120 ++++++++++++ .../Unit/Observer/AfterAdminUserSaveTest.php | 159 ++++++++++++++++ .../BeforeAdminUserAuthenticateTest.php | 119 ++++++++++++ .../Magento/Security/etc/adminhtml/di.xml | 3 + .../Magento/Security/etc/adminhtml/events.xml | 14 ++ app/code/Magento/Security/etc/crontab.xml | 3 + app/code/Magento/Security/etc/db_schema.xml | 11 ++ .../Security/etc/db_schema_whitelist.json | 10 + app/code/Magento/Security/i18n/en_US.csv | 1 + .../Model/AdminTokenServiceTest.php | 19 ++ .../Model/AdminSessionsManagerTest.php | 27 ++- .../UserExpiration/CollectionTest.php | 62 ++++++ .../Model/UserExpirationManagerTest.php | 148 +++++++++++++++ .../Observer/AfterAdminUserLoadTest.php | 49 +++++ .../Observer/AfterAdminUserSaveTest.php | 101 ++++++++++ .../BeforeAdminUserAuthenticateTest.php | 47 +++++ .../Magento/Security/_files/expired_users.php | 71 +++++++ 30 files changed, 1742 insertions(+), 41 deletions(-) create mode 100644 app/code/Magento/Security/Model/Plugin/UserValidationRules.php create mode 100644 app/code/Magento/Security/Model/ResourceModel/UserExpiration.php create mode 100644 app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php create mode 100644 app/code/Magento/Security/Model/UserExpiration.php create mode 100644 app/code/Magento/Security/Model/UserExpiration/Validator.php create mode 100644 app/code/Magento/Security/Model/UserExpirationManager.php create mode 100644 app/code/Magento/Security/Observer/AfterAdminUserLoad.php create mode 100644 app/code/Magento/Security/Observer/AfterAdminUserSave.php create mode 100644 app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php create mode 100644 app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php create mode 100644 app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php create mode 100644 app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php create mode 100644 app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php create mode 100644 app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php create mode 100644 app/code/Magento/Security/etc/adminhtml/events.xml create mode 100644 dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 2a9024c54ee95..0c1cca617cbc8 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -27,11 +27,6 @@ class AdminSessionsManager */ const LOGOUT_REASON_USER_LOCKED = 10; - /** - * User has been logged out due to an expired user account - */ - const LOGOUT_REASON_USER_EXPIRED = 11; - /** * @var ConfigInterface * @since 100.1.0 @@ -80,6 +75,12 @@ class AdminSessionsManager */ private $maxIntervalBetweenConsecutiveProlongs = 60; + /** + * TODO: make sure we need this here + * @var UserExpirationManager + */ + private $userExpirationManager; + /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession @@ -87,6 +88,7 @@ class AdminSessionsManager * @param CollectionFactory $adminSessionInfoCollectionFactory * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param RemoteAddress $remoteAddress + * @param UserExpirationManager|null $userExpirationManager */ public function __construct( ConfigInterface $securityConfig, @@ -94,7 +96,8 @@ public function __construct( \Magento\Security\Model\AdminSessionInfoFactory $adminSessionInfoFactory, \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionInfoCollectionFactory, \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - RemoteAddress $remoteAddress + RemoteAddress $remoteAddress, + \Magento\Security\Model\UserExpirationManager $userExpirationManager = null ) { $this->securityConfig = $securityConfig; $this->authSession = $authSession; @@ -102,6 +105,9 @@ public function __construct( $this->adminSessionInfoCollectionFactory = $adminSessionInfoCollectionFactory; $this->dateTime = $dateTime; $this->remoteAddress = $remoteAddress; + $this->userExpirationManager = $userExpirationManager ?: + \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Security\Model\UserExpirationManager::class); } /** @@ -127,10 +133,6 @@ public function processLogin() } } - if ($this->authSession->getUser()->getExpiresAt()) { - $this->revokeExpiredAdminUser(); - } - return $this; } @@ -142,7 +144,33 @@ public function processLogin() */ public function processProlong() { + // TODO: is this the right place for this? Or should I use a plugin? This method is called in a plugin + // also, don't want to hit the database every single time. We could put it within the lastProlongIsOldEnough + // in order to reduece database loads, but what if the user is expired already? How granular do we want to get? + // if their session is expired, then they will get logged out anyways, and we can handle deactivating them + // upon login or via the cron + + // already (\Magento\Security\Model\Plugin\AuthSession::aroundProlong, which plugs into + // \Magento\Backend\Model\Auth\Session::prolong, which is called from + // \Magento\Backend\App\Action\Plugin\Authentication::aroundDispatch, which is a plugin to + // \Magento\Backend\App\AbstractAction::dispatch) + + // \Magento\Backend\App\AbstractAction::dispatch is called, which kicks off the around plugin + // \Magento\Backend\App\Action\Plugin\Authentication::aroundDispatch, which calls + // \Magento\Backend\Model\Auth\Session::prolong, which kicks off the around plugin + // \Magento\Security\Model\Plugin\AuthSession::aroundProlong, which calls + // this method. + + // this method will prolong the session only if it's old enough, otherwise it's not called. +// if ($this->userExpirationManager->userIsExpired($this->authSession->getUser())) { +// $this->userExpirationManager->deactivateExpiredUsers([$this->authSession->getUser()->getId()]); +// } + if ($this->lastProlongIsOldEnough()) { + // TODO: throw exception? + if ($this->userExpirationManager->userIsExpired($this->authSession->getUser())) { + $this->userExpirationManager->deactivateExpiredUsers([$this->authSession->getUser()->getId()]); + } $this->getCurrentSession()->setData( 'updated_at', date( @@ -153,11 +181,6 @@ public function processProlong() $this->getCurrentSession()->save(); } - // todo: don't necessarily have a user here - if ($this->authSession->getUser()->getExpiresAt()) { - $this->revokeExpiredAdminUser(); - } - return $this; } @@ -223,11 +246,6 @@ public function getLogoutReasonMessageByStatus($statusCode) 'Your account is temporarily disabled. Please try again later.' ); break; - case self::LOGOUT_REASON_USER_EXPIRED: - $reasonMessage = __( - 'Your account has expired.' - ); - break; default: $reasonMessage = __('Your current session has been expired.'); break; @@ -373,21 +391,4 @@ private function getIntervalBetweenConsecutiveProlongs() ) ); } - - /** - * Check if the current user is expired and, if so, revoke their admin token. - */ - private function revokeExpiredAdminUser() - { - $expiresAt = $this->dateTime->gmtTimestamp($this->authSession->getUser()->getExpiresAt()); - if ($expiresAt < $this->dateTime->gmtTimestamp()) { - $currentSessions = $this->getSessionsForCurrentUser(); - $currentSessions->setDataToAll('status', self::LOGOUT_REASON_USER_EXPIRED) - ->save(); - $this->authSession->getUser() - ->setIsActive(0) - ->setExpiresAt(null) - ->save(); - } - } } diff --git a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php new file mode 100644 index 0000000000000..75b826dfb6b65 --- /dev/null +++ b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php @@ -0,0 +1,40 @@ +validator = $validator; + } + + /** + * @param \Magento\User\Model\UserValidationRules $userValidationRules + * @param \Magento\Framework\Validator\DataObject $result + * @return \Magento\Framework\Validator\DataObject + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAddUserInfoRules(\Magento\User\Model\UserValidationRules $userValidationRules, $result) + { + return $result->addRule($this->validator, 'expires_at'); + } +} diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php new file mode 100644 index 0000000000000..5afca619c3f7f --- /dev/null +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -0,0 +1,49 @@ +_init('admin_user_expiration', 'user_id'); + } + + /** + * Perform actions before object save + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function _beforeSave(\Magento\Framework\Model\AbstractModel $object) + { + /** @var $object \Magento\Security\Model\UserExpiration */ + if ($object->getExpiresAt() instanceof \DateTimeInterface) { + + // TODO: use this? need to check if we're ever passing in a \DateTimeInterface or if it's always a string + $object->setExpiresAt($object->getExpiresAt()->format('Y-m-d H:i:s')); + } + + return $this; + } +} diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php new file mode 100644 index 0000000000000..08c72f7d9fd2f --- /dev/null +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php @@ -0,0 +1,75 @@ +_init( + \Magento\Security\Model\UserExpiration::class, + \Magento\Security\Model\ResourceModel\UserExpiration::class + ); + } + + /** + * Filter for expired, active users. + * + * @param string $now + * @return $this + */ + public function addActiveExpiredUsersFilter($now = null): Collection + { + if ($now === null) { + $now = new \DateTime(); + $now->format('Y-m-d H:i:s'); + } + $this->getSelect()->joinLeft( + ['user' => $this->getTable('admin_user')], + 'main_table.user_id = user.user_id', + ['is_active'] + ); + $this->addFieldToFilter('expires_at', ['lt' => $now]) + ->addFieldToFilter('user.is_active', 1); + + return $this; + } + + /** + * Filter collection by user id. + * @param array $userIds + * @return Collection + */ + public function addUserIdsFilter($userIds = []): Collection + { + return $this->addFieldToFilter('main_table.user_id', ['in' => $userIds]); + } + + /** + * Get any expired records for the given user. + * + * @param $userId + * @return Collection + */ + public function addExpiredRecordsForUserFilter($userId): Collection + { + return $this->addActiveExpiredUsersFilter() + ->addFieldToFilter('main_table.user_id', $userId); + } +} diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php new file mode 100644 index 0000000000000..eaf2259375f0a --- /dev/null +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -0,0 +1,62 @@ +validator = $validator; + } + + /** + * Resource initialization + * + * @return void + */ + protected function _construct() + { + $this->_init(\Magento\Security\Model\ResourceModel\UserExpiration::class); + } + + /** + * TODO: remove and use a plugin on UserValidationRules + */ +// protected function _getValidationRulesBeforeSave() +// { +// return $this->validator; +// } +} diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php new file mode 100644 index 0000000000000..4b82f2ab43f07 --- /dev/null +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -0,0 +1,45 @@ +_clearMessages(); + $messages = []; + $expiresAt = $value; + $label = 'Expiration date'; + if (\Zend_Validate::is($expiresAt, 'NotEmpty')) { + $currentTime = new \DateTime(); + $expiresAt = new \DateTime($expiresAt); + + if ($expiresAt < $currentTime) { + $messages['expires_at'] = __('"%1" must be later than the current date.', $label); + } + } + $this->_addMessages($messages); + + return empty($messages); + } +} diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php new file mode 100644 index 0000000000000..bd10b55e1b36d --- /dev/null +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -0,0 +1,179 @@ +dateTime = $dateTime; + $this->securityConfig = $securityConfig; + $this->adminSessionInfoCollectionFactory = $adminSessionInfoCollectionFactory; + $this->authSession = $authSession; + $this->userExpirationCollectionFactory = $userExpirationCollectionFactory; + $this->userCollectionFactory = $userCollectionFactory; + } + + /** + * Revoke admin tokens for expired users. + * TODO: any better way than looping? + * TODO: remove + * @param \Magento\User\Model\User $user + * @throws \Exception + */ + public function deactivateExpiredUser(\Magento\User\Model\User $user): void + { + $currentSessions = $this->getSessionsForUser($user); + $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) + ->save(); + $user + ->setIsActive(0) + ->save(); + // TODO: remove expires_at record from new table + } + + /** + * Deactivate expired user accounts and invalidate their sessions. + * + * @param array|null $userIds + */ + public function deactivateExpiredUsers(?array $userIds = null): void + { + /** @var ExpiredUsersCollection $expiredRecords */ + $expiredRecords = $this->userExpirationCollectionFactory->create()->addActiveExpiredUsersFilter(); + if ($userIds != null) { + $expiredRecords->addUserIdsFilter($userIds); + } + + if ($expiredRecords->getSize() > 0) { + // get all active sessions for the users and set them to logged out + /** @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection $currentSessions */ + $currentSessions = $this->adminSessionInfoCollectionFactory->create() + ->addFieldToFilter('user_id', ['in' => $expiredRecords->getAllIds()]) + ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()); + $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) + ->save(); + } + + // delete expired records + $expiredRecordIds = $expiredRecords->getAllIds(); + $expiredRecords->walk('delete'); + + // set user is_active to 0 + $users = $this->userCollectionFactory->create() + ->addFieldToFilter('main_table.user_id', ['in' => $expiredRecordIds]); + $users->setDataToAll('is_active', 0)->save(); + } + + /** + * Get sessions for the given user. + * TODO: remove + * @param \Magento\User\Model\User $user + * @return ResourceModel\AdminSessionInfo\Collection + */ + private function getSessionsForUser(\Magento\User\Model\User $user) + { + $collection = $this->adminSessionInfoCollectionFactory->create(); + $collection + ->filterByUser($user->getId(), \Magento\Security\Model\AdminSessionInfo::LOGGED_IN) + ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()) + ->loadData(); + + return $collection; + } + + /** + * Check if the given user is expired. + * // TODO: check users expired an hour ago (timezone stuff) + * @param \Magento\User\Model\User $user + * @return bool + */ + public function userIsExpired(\Magento\User\Model\User $user): bool + { + $isExpired = false; + $expiredRecord = $this->userExpirationCollectionFactory->create() + ->addExpiredRecordsForUserFilter($user->getId()) + ->getFirstItem(); // expiresAt: 1561824907, current timestamp: 1561824932 + if ($expiredRecord && $expiredRecord->getId()) { + //$expiresAt = $this->dateTime->gmtTimestamp($expiredRecord->getExpiredAt()); + $expiresAt = $this->dateTime->timestamp($expiredRecord->getExpiresAt()); + $isExpired = $expiresAt < $this->dateTime->gmtTimestamp(); + } + + return $isExpired; + } + + /** + * Check if the current user is expired and, if so, revoke their admin token. + */ + // private function revokeExpiredAdminUser() + // { + // $expiresAt = $this->dateTime->gmtTimestamp($this->authSession->getUser()->getExpiresAt()); + // if ($expiresAt < $this->dateTime->gmtTimestamp()) { + // $currentSessions = $this->getSessionsForCurrentUser(); + // $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) + // ->save(); + // $this->authSession->getUser() + // ->setIsActive(0) + // ->setExpiresAt(null) + // ->save(); + // } + // } +} diff --git a/app/code/Magento/Security/Observer/AfterAdminUserLoad.php b/app/code/Magento/Security/Observer/AfterAdminUserLoad.php new file mode 100644 index 0000000000000..4bc6804276c5c --- /dev/null +++ b/app/code/Magento/Security/Observer/AfterAdminUserLoad.php @@ -0,0 +1,54 @@ +userExpirationFactory = $userExpirationFactory; + $this->userExpirationResource = $userExpirationResource; + } + + /** + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer) + { + /* @var $user \Magento\User\Model\User */ + $user = $observer->getEvent()->getObject(); + if ($user->getId()) { + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $this->userExpirationFactory->create(); + $this->userExpirationResource->load($userExpiration, $user->getId()); + if ($userExpiration->getExpiresAt()) { + $user->setExpiresAt($userExpiration->getExpiresAt()); + } + } + } +} diff --git a/app/code/Magento/Security/Observer/AfterAdminUserSave.php b/app/code/Magento/Security/Observer/AfterAdminUserSave.php new file mode 100644 index 0000000000000..37fb6af3ccaa6 --- /dev/null +++ b/app/code/Magento/Security/Observer/AfterAdminUserSave.php @@ -0,0 +1,65 @@ +userExpirationFactory = $userExpirationFactory; + $this->userExpirationResource = $userExpirationResource; + } + + /** + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer) + { + /* @var $user \Magento\User\Model\User */ + $user = $observer->getEvent()->getObject(); + if ($user->getId()) { + $expiresAt = $user->getExpiresAt(); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $this->userExpirationFactory->create(); + $this->userExpirationResource->load($userExpiration, $user->getId()); + + if (empty($expiresAt)) { + // delete it if the admin user clears the field + if ($userExpiration->getId()) { + $this->userExpirationResource->delete($userExpiration); + } + } else { + if (!$userExpiration->getId()) { + $userExpiration->setId($user->getId()); + } + $userExpiration->setExpiresAt($expiresAt); + $this->userExpirationResource->save($userExpiration); + } + } + } +} diff --git a/app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php b/app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php new file mode 100644 index 0000000000000..1c888ec511646 --- /dev/null +++ b/app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php @@ -0,0 +1,61 @@ +userExpirationManager = $userExpirationManager; + $this->user = $user; + } + + /** + * @param Observer $observer + * @return void + * @throws AuthenticationException + */ + public function execute(Observer $observer) + { + $username = $observer->getEvent()->getUsername(); + /** @var \Magento\User\Model\User $user */ + $user = $this->user->loadByUsername($username); + + if ($this->userExpirationManager->userIsExpired($user)) { + $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + throw new AuthenticationException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); + } + } +} diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d7495cd9ce173..a062cad9d61da 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -50,7 +50,7 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ protected $objectManager; - /* + /** * @var RemoteAddress */ protected $remoteAddressMock; diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php new file mode 100644 index 0000000000000..4498e514e45d3 --- /dev/null +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php @@ -0,0 +1,63 @@ +createMock(\Magento\Security\Model\UserExpiration\Validator::class); + $this->userValidationRules = $this->createMock(\Magento\User\Model\UserValidationRules::class); + $this->rules = $objectManager->getObject(\Magento\User\Model\UserValidationRules::class); + $this->validator = $this->createMock(\Magento\Framework\Validator\DataObject::class); + $this->plugin = + $objectManager->getObject( + \Magento\Security\Model\Plugin\UserValidationRules::class, + ['validator' => $userExpirationValidator] + ); + } + + public function testAfterAddUserInfoRules() + { + $this->validator->expects(static::exactly(5))->method('addRule')->willReturn($this->validator); + static::assertSame($this->validator, $this->rules->addUserInfoRules($this->validator)); + static::assertSame($this->validator, $this->callAfterAddUserInfoRulesPlugin($this->validator)); + } + + protected function callAfterAddUserInfoRulesPlugin($validator) + { + return $this->plugin->afterAddUserInfoRules($this->userValidationRules, $validator); + } +} diff --git a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php new file mode 100644 index 0000000000000..18e1eb3438bae --- /dev/null +++ b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php @@ -0,0 +1,49 @@ +validator = $objectManager->getObject(\Magento\Security\Model\UserExpiration\Validator::class); + } + + public function testWithPastDate() + { + $testDate = new \DateTime(); + $testDate->modify('-10 days'); + static::assertFalse($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); + static::assertContains( + '"Expiration date" must be later than the current date.', + $this->validator->getMessages() + ); + } + + public function testWithFutureDate() + { + $testDate = new \DateTime(); + $testDate->modify('+10 days'); + static::assertTrue($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); + static::assertEquals([], $this->validator->getMessages()); + } +} diff --git a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php new file mode 100644 index 0000000000000..dfcbcad5780b8 --- /dev/null +++ b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php @@ -0,0 +1,120 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->userExpirationFactoryMock = $this->createMock(\Magento\Security\Model\UserExpirationFactory::class); + $this->userExpirationResourceMock = $this->createPartialMock( + \Magento\Security\Model\ResourceModel\UserExpiration::class, + ['load'] + ); + $this->observer = $this->objectManager->getObject( + \Magento\Security\Observer\AfterAdminUserLoad::class, + [ + 'userExpirationFactory' => $this->userExpirationFactoryMock, + 'userExpirationResource' => $this->userExpirationResourceMock, + ] + ); + + $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); + $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getObject']); + $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['getId', 'setExpiresAt']); + $this->userExpirationMock = $this->createPartialMock( + \Magento\Security\Model\UserExpiration::class, + ['getExpiresAt'] + ); + } + + public function testWithExpiredUser() + { + $userId = '123'; + $testDate = new \DateTime(); + $testDate->modify('+10 days'); + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); + $this->userExpirationFactoryMock->expects(static::once()) + ->method('create') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once()) + ->method('load') + ->willReturn($this->userExpirationMock); + $this->userExpirationMock->expects(static::exactly(2)) + ->method('getExpiresAt') + ->willReturn($testDate->format('Y-m-d H:i:s')); + $this->userMock->expects(static::once()) + ->method('setExpiresAt') + ->willReturn($this->userMock); + $this->observer->execute($this->eventObserverMock); + } + + public function testWithNonExpiredUser() + { + $userId = '123'; + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); + $this->userExpirationFactoryMock->expects(static::once())->method('create') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('load') + ->willReturn($this->userExpirationMock); + $this->userExpirationMock->expects(static::once()) + ->method('getExpiresAt') + ->willReturn(null); + $this->observer->execute($this->eventObserverMock); + } +} diff --git a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php new file mode 100644 index 0000000000000..439ec3f88548b --- /dev/null +++ b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php @@ -0,0 +1,159 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->userExpirationFactoryMock = $this->createMock(\Magento\Security\Model\UserExpirationFactory::class); + $this->userExpirationResourceMock = $this->createPartialMock( + \Magento\Security\Model\ResourceModel\UserExpiration::class, + ['load', 'save', 'delete'] + ); + $this->observer = $this->objectManager->getObject( + \Magento\Security\Observer\AfterAdminUserSave::class, + [ + 'userExpirationFactory' => $this->userExpirationFactoryMock, + 'userExpirationResource' => $this->userExpirationResourceMock, + ] + ); + $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); + $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getObject']); + $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['getId', 'getExpiresAt']); + $this->userExpirationMock = $this->createPartialMock( + \Magento\Security\Model\UserExpiration::class, + ['getId', 'getExpiresAt', 'setId', 'setExpiresAt'] + ); + } + + public function testSaveNewUserExpiration() + { + $userId = '123'; + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); + $this->userMock->expects(static::exactly(3))->method('getId')->willReturn($userId); + $this->userMock->expects(static::once())->method('getExpiresAt')->willReturn($this->getExpiresDateTime()); + $this->userExpirationFactoryMock->expects(static::once())->method('create') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('load') + ->willReturn($this->userExpirationMock); + + $this->userExpirationMock->expects(static::once())->method('getId')->willReturn(null); + $this->userExpirationMock->expects(static::once())->method('setId')->willReturn($this->userExpirationMock); + $this->userExpirationMock->expects(static::once())->method('setExpiresAt') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('save') + ->willReturn($this->userExpirationResourceMock); + $this->observer->execute($this->eventObserverMock); + } + + /** + * @throws \Exception + */ + public function testClearUserExpiration() + { + $userId = '123'; + $this->userExpirationMock->setId($userId); + + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); + $this->userMock->expects(static::once())->method('getExpiresAt')->willReturn(null); + $this->userExpirationFactoryMock->expects(static::once())->method('create') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('load') + ->willReturn($this->userExpirationMock); + + $this->userExpirationMock->expects(static::once())->method('getId')->willReturn($userId); + $this->userExpirationResourceMock->expects(static::once())->method('delete') + ->willReturn($this->userExpirationResourceMock); + $this->observer->execute($this->eventObserverMock); + } + + public function testChangeUserExpiration() + { + $userId = '123'; + $this->userExpirationMock->setId($userId); + + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); + $this->userMock->expects(static::once())->method('getExpiresAt')->willReturn($this->getExpiresDateTime()); + $this->userExpirationFactoryMock->expects(static::once())->method('create') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('load') + ->willReturn($this->userExpirationMock); + + $this->userExpirationMock->expects(static::once())->method('getId')->willReturn($userId); + $this->userExpirationMock->expects(static::once())->method('setExpiresAt') + ->willReturn($this->userExpirationMock); + $this->userExpirationResourceMock->expects(static::once())->method('save') + ->willReturn($this->userExpirationResourceMock); + $this->observer->execute($this->eventObserverMock); + } + + /** + * @return string + * @throws \Exception + */ + private function getExpiresDateTime() + { + $testDate = new \DateTime(); + $testDate->modify('+10 days'); + return $testDate->format('Y-m-d H:i:s'); + } +} diff --git a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php new file mode 100644 index 0000000000000..9df2a133ff2a9 --- /dev/null +++ b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php @@ -0,0 +1,119 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->userExpirationManagerMock = $this->createPartialMock( + \Magento\Security\Model\UserExpirationManager::class, + ['userIsExpired', 'deactivateExpiredUsers'] + ); + $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['loadByUsername', 'getId']); + $this->observer = $this->objectManager->getObject( + \Magento\Security\Observer\BeforeAdminUserAuthenticate::class, + [ + 'userExpirationManager' => $this->userExpirationManagerMock, + 'user' => $this->userMock, + ] + ); + $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); + $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getUsername']); + $this->userExpirationMock = $this->createPartialMock( + \Magento\Security\Model\UserExpiration::class, + ['getId', 'getExpiresAt', 'setId', 'setExpiresAt'] + ); + } + + /** + * @expectedException \Magento\Framework\Exception\Plugin\AuthenticationException + * @expectedExceptionMessage The account sign-in was incorrect or your account is disabled temporarily. + * Please wait and try again later + */ + public function testWithExpiredUser() + { + $adminUserId = 123; + $username = 'testuser'; + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getUsername')->willReturn($username); + $this->userMock->expects(static::once())->method('loadByUsername')->willReturn($this->userMock); + + $this->userExpirationManagerMock->expects(static::once()) + ->method('userIsExpired') + ->with($this->userMock) + ->willReturn(true); + $this->userMock->expects(static::once())->method('getId')->willReturn($adminUserId); + $this->userExpirationManagerMock->expects(static::once()) + ->method('deactivateExpiredUsers') + ->with([$adminUserId]) + ->willReturn(null); + $this->observer->execute($this->eventObserverMock); + } + + public function testWithNonExpiredUser() + { + $username = 'testuser'; + $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); + $this->eventMock->expects(static::once())->method('getUsername')->willReturn($username); + $this->userMock->expects(static::once())->method('loadByUsername')->willReturn($this->userMock); + + $this->userExpirationManagerMock->expects(static::once()) + ->method('userIsExpired') + ->with($this->userMock) + ->willReturn(false); + $this->observer->execute($this->eventObserverMock); + } +} diff --git a/app/code/Magento/Security/etc/adminhtml/di.xml b/app/code/Magento/Security/etc/adminhtml/di.xml index 79477e9443097..8cf5b610ef3c5 100644 --- a/app/code/Magento/Security/etc/adminhtml/di.xml +++ b/app/code/Magento/Security/etc/adminhtml/di.xml @@ -15,6 +15,9 @@ + + + Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST diff --git a/app/code/Magento/Security/etc/adminhtml/events.xml b/app/code/Magento/Security/etc/adminhtml/events.xml new file mode 100644 index 0000000000000..1bd8e9807c05f --- /dev/null +++ b/app/code/Magento/Security/etc/adminhtml/events.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Security/etc/crontab.xml b/app/code/Magento/Security/etc/crontab.xml index a30a43730e6fa..203cf5c2a2389 100644 --- a/app/code/Magento/Security/etc/crontab.xml +++ b/app/code/Magento/Security/etc/crontab.xml @@ -13,5 +13,8 @@ 0 0 * * * + + 0 * * * * + diff --git a/app/code/Magento/Security/etc/db_schema.xml b/app/code/Magento/Security/etc/db_schema.xml index ce7143582ce69..ec0f1d72c604e 100644 --- a/app/code/Magento/Security/etc/db_schema.xml +++ b/app/code/Magento/Security/etc/db_schema.xml @@ -55,4 +55,15 @@ + + + + + + + +
diff --git a/app/code/Magento/Security/etc/db_schema_whitelist.json b/app/code/Magento/Security/etc/db_schema_whitelist.json index c387b7591c7a5..1f8183e123956 100644 --- a/app/code/Magento/Security/etc/db_schema_whitelist.json +++ b/app/code/Magento/Security/etc/db_schema_whitelist.json @@ -33,5 +33,15 @@ "constraint": { "PRIMARY": true } + }, + "admin_user_expiration": { + "column": { + "user_id": true, + "expires_at": true + }, + "constraint": { + "PRIMARY": true, + "ADMIN_USER_EXPIRATION_USER_ID_ADMIN_USER_USER_ID": true + } } } \ No newline at end of file diff --git a/app/code/Magento/Security/i18n/en_US.csv b/app/code/Magento/Security/i18n/en_US.csv index 0cf998b21a1c8..4329e0747d08e 100644 --- a/app/code/Magento/Security/i18n/en_US.csv +++ b/app/code/Magento/Security/i18n/en_US.csv @@ -26,3 +26,4 @@ None,None "Limit the number of password reset request per hour. Use 0 to disable.","Limit the number of password reset request per hour. Use 0 to disable." "Min Time Between Password Reset Requests","Min Time Between Password Reset Requests" "Delay in minutes between password reset requests. Use 0 to disable.","Delay in minutes between password reset requests. Use 0 to disable." +"""%1"" must be later than the current date.","""%1"" must be later than the current date." diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php b/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php index 369d71ddbff9b..21a9f6d942dc3 100644 --- a/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php @@ -59,6 +59,25 @@ public function testCreateAdminAccessToken() $this->assertEquals($accessToken, $token); } + /** + * TODO: fix failing test + * @magentoDataFixture Magento/Security/_files/expired_users.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testCreateAdminAccessTokenExpiredUser() + { + $adminUserNameFromFixture = 'adminUserExpired'; + $this->tokenService->createAdminAccessToken( + $adminUserNameFromFixture, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + + $this->expectExceptionMessage( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ); + } + /** * @dataProvider validationDataProvider */ diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php index a201dbfdf1b03..74cefb9d41a59 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php @@ -3,8 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Security\Model; +/** + * Class AdminSessionsManagerTest + * TODO: test AdminSessionsManager::processLogin + * TODO: test AdminSessionsManager::processProlong + * + * @package Magento\Security\Model + */ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase { /** @@ -61,8 +69,8 @@ protected function setUp() protected function tearDown() { $this->auth = null; - $this->authSession = null; - $this->adminSessionInfo = null; + $this->authSession = null; + $this->adminSessionInfo = null; $this->adminSessionsManager = null; $this->objectManager = null; parent::tearDown(); @@ -125,10 +133,23 @@ public function testTerminateOtherSessionsProcessLogin() $session->load('669e2e3d752e8', 'session_id'); $this->assertEquals( AdminSessionInfo::LOGGED_OUT_BY_LOGIN, - (int) $session->getStatus() + (int)$session->getStatus() ); } + /** + * Test that expired users cannot login. + * + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedException \Magento\Framework\Exception\AuthenticationException + * @magentoDataFixture Magento/Security/_files/expired_users.php + * @magentoDbIsolation enabled + */ + public function testExpiredUserProcessLogin() + { + static::markTestSkipped('to implement'); + } + /** * Test if current session is retrieved * diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php new file mode 100644 index 0000000000000..63f1c377f99cc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php @@ -0,0 +1,62 @@ +objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->collectionModelFactory = $this->objectManager + ->create(\Magento\Security\Model\ResourceModel\UserExpiration\CollectionFactory::class); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testExpiredActiveUsersFilter() + { + /** @var \Magento\Security\Model\ResourceModel\UserExpiration\Collection $collectionModel */ + $collectionModel = $this->collectionModelFactory->create(); + $collectionModel->addActiveExpiredUsersFilter(); + static::assertGreaterThanOrEqual(1, $collectionModel->getSize()); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testGetExpiredRecordsForUser() + { + $adminUserNameFromFixture = 'adminUserExpired'; + $user = $this->objectManager->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + + /** @var \Magento\Security\Model\ResourceModel\UserExpiration\Collection $collectionModel */ + $collectionModel = $this->collectionModelFactory->create()->addExpiredRecordsForUserFilter($user->getId()); + static::assertGreaterThanOrEqual(1, $collectionModel->getSize()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php new file mode 100644 index 0000000000000..1840748444842 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -0,0 +1,148 @@ +getUserFromUserName($adminUserNameFromFixture); + /** @var \Magento\Security\Model\UserExpirationManager $userExpirationManager */ + $userExpirationManager = Bootstrap::getObjectManager() + ->create(\Magento\Security\Model\UserExpirationManager::class); + static::assertTrue($userExpirationManager->userIsExpired($user)); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testDeactivateExpiredUsersWithExpiredUser() + { + $adminUserNameFromFixture = 'adminUserExpired'; + + list($user, $token, $expiredUserModel) = $this->setupCronTests($adminUserNameFromFixture); + + static::assertEquals(0, $user->getIsActive()); + static::assertEquals(null, $token->getId()); + static::assertNull($expiredUserModel->getId()); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testDeactivateExpiredUsersWithNonExpiredUser() + { + $adminUserNameFromFixture = 'adminUserNotExpired'; + // log them in + $adminToken = $this->createToken($adminUserNameFromFixture); + + list($user, $token, $expiredUserModel) = $this->setupCronTests($adminUserNameFromFixture); + + static::assertEquals(1, $user->getIsActive()); + static::assertNotNull($token->getId()); + static::assertEquals($expiredUserModel->getUserId(), $user->getId()); + } + + /** + * @param string $adminUserNameFromFixture + * @return array + */ + private function setupCronTests(string $adminUserNameFromFixture): array + { + // TODO: set the user expired after calling this + // TODO: use this to test the observer with exception: + // Magento\Framework\Exception\Plugin\AuthenticationException : The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later. + //$adminToken = $this->createToken($adminUserNameFromFixture); // TODO: this logs the user in, which kicks off the deactivate call + + /** @var \Magento\Security\Model\UserExpirationManager $job */ + $userExpirationManager = Bootstrap::getObjectManager() + ->create(\Magento\Security\Model\UserExpirationManager::class); + $userExpirationManager->deactivateExpiredUsers(); + + /** @var \Magento\User\Model\User $user */ + $user = $this->getUserFromUserName($adminUserNameFromFixture); + + // this is for the API only + $oauthToken = $this->getOauthTokenByUser($user); + $expiredUserModel = $this->getExpiredUserModelByUser($user); + + return [$user, $oauthToken, $expiredUserModel]; + } + + /** + * TODO: this calls user->login and throws an AuthenticationException + * + * @param string $adminUserNameFromFixture + * @return string + * @throws \Magento\Framework\Exception\AuthenticationException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function createToken(string $adminUserNameFromFixture): string + { + /** @var \Magento\Integration\Api\AdminTokenServiceInterface $tokenService */ + $tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Api\AdminTokenServiceInterface::class); + $token = $tokenService->createAdminAccessToken( + $adminUserNameFromFixture, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + + return $token; + } + + /** + * @param string $adminUserNameFromFixture + * @return \Magento\User\Model\User + */ + private function getUserFromUserName(string $adminUserNameFromFixture): \Magento\User\Model\User + { + /** @var \Magento\User\Model\User $user */ + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + return $user; + } + + /** + * @param \Magento\User\Model\User $user + * @return \Magento\Integration\Model\Oauth\Token + */ + private function getOauthTokenByUser(\Magento\User\Model\User $user): \Magento\Integration\Model\Oauth\Token + { + /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ + $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); + $oauthToken = $tokenModel->loadByAdminId($user->getId()); + return $oauthToken; + } + + /** + * @param \Magento\User\Model\User $user + * @return UserExpiration + */ + private function getExpiredUserModelByUser(\Magento\User\Model\User $user): \Magento\Security\Model\UserExpiration + { + /** @var \Magento\Security\Model\UserExpiration $expiredUserModel */ + $expiredUserModel = Bootstrap::getObjectManager()->get(\Magento\Security\Model\UserExpiration::class); + $expiredUserModel->load($user->getId()); + return $expiredUserModel; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php new file mode 100644 index 0000000000000..0fcd90913e562 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php @@ -0,0 +1,49 @@ +create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + $userId = $user->getId(); + $loadedUser = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $loadedUser->load($userId); + static::assertNotNull($loadedUser->getExpiresAt()); + } + + /** + * @magentoDataFixture Magento/User/_files/dummy_user.php + */ + public function testWithNonExpiredUser() + { + $adminUserNameFromFixture = 'dummy_username'; + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + $userId = $user->getId(); + $loadedUser = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $loadedUser->load($userId); + static::assertNull($loadedUser->getExpiresAt()); + } + +} diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php new file mode 100644 index 0000000000000..6032b651b4dc3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php @@ -0,0 +1,101 @@ +getExpiresDateTime(); + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + $user->setExpiresAt($testDate); + $user->save(); + + $userExpirationFactory = + Bootstrap::getObjectManager()->create(\Magento\Security\Model\UserExpirationFactory::class); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $userExpirationFactory->create(); + $userExpiration->load($user->getId()); + static::assertNotNull($userExpiration->getId()); + static::assertEquals($userExpiration->getExpiresAt(), $testDate); + } + + /** + * Remove the UserExpiration record + * + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testClearUserExpiration() + { + $adminUserNameFromFixture = 'adminUserExpired'; + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + $user->setExpiresAt(null); + $user->save(); + + $userExpirationFactory = + Bootstrap::getObjectManager()->create(\Magento\Security\Model\UserExpirationFactory::class); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $userExpirationFactory->create(); + $userExpiration->load($user->getId()); + static::assertNull($userExpiration->getId()); + } + + /** + * Change the UserExpiration record + * + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testChangeUserExpiration() + { + $adminUserNameFromFixture = 'adminUserNotExpired'; + $testDate = $this->getExpiresDateTime(); + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + + $userExpirationFactory = + Bootstrap::getObjectManager()->create(\Magento\Security\Model\UserExpirationFactory::class); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $userExpirationFactory->create(); + $userExpiration->load($user->getId()); + $existingExpiration = $userExpiration->getExpiresAt(); + + $user->setExpiresAt($testDate); + $user->save(); + $userExpiration->load($user->getId()); + static::assertNotNull($userExpiration->getId()); + static::assertEquals($userExpiration->getExpiresAt(), $testDate); + static::assertNotEquals($existingExpiration, $userExpiration->getExpiresAt()); + } + + /** + * @return string + * @throws \Exception + */ + private function getExpiresDateTime() + { + $testDate = new \DateTime(); + $testDate->modify('+20 days'); + return $testDate->format('Y-m-d H:i:s'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php new file mode 100644 index 0000000000000..db7a83877d1b2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php @@ -0,0 +1,47 @@ +create(\Magento\User\Model\User::class); + $user->authenticate($adminUserNameFromFixture, $password); + static::assertFalse((bool)$user->getIsActive()); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testWithNonExpiredUser() + { + $adminUserNameFromFixture = 'adminUserNotExpired'; + $password = \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD; + /** @var \Magento\User\Model\User $user */ + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->authenticate($adminUserNameFromFixture, $password); + static::assertTrue((bool)$user->getIsActive()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php new file mode 100644 index 0000000000000..5a3971cd9d0bb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php @@ -0,0 +1,71 @@ +create(\Magento\User\Model\User::class); +$userModelNotExpired->setFirstName("John") + ->setLastName("Doe") + ->setUserName('adminUserNotExpired') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUserNotExpired@example.com') + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow') + ->setIsActive(1) + ->save(); +$futureDate = new \DateTime(); +$futureDate->modify('+10 days'); +$notExpiredRecord = $objectManager->create(\Magento\Security\Model\UserExpiration::class); +$notExpiredRecord + ->setId($userModelNotExpired->getId()) + ->setExpiresAt($futureDate->format('Y-m-d H:i:s')) + ->save(); + +/** @var $userModelExpired \Magento\User\Model\User */ +$pastDate = new \DateTime(); +$pastDate->modify('-10 days'); +$userModelExpired = $objectManager->create(\Magento\User\Model\User::class); +$userModelExpired->setFirstName("John") + ->setLastName("Doe") + ->setUserName('adminUserExpired') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUserExpired@example.com') + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow') + ->setIsActive(1) + ->save(); +$expiredRecord = $objectManager->create(\Magento\Security\Model\UserExpiration::class); +$expiredRecord + ->setId($userModelExpired->getId()) + ->setExpiresAt($pastDate->format('Y-m-d H:i:s')) + ->save(); + +// TODO: remove +// need to bypass model validation to set expired date +//$pastDate = new \DateTime(); +//$pastDate->modify('-10 days'); +//$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +///** @var \Magento\Framework\DB\Adapter\AdapterInterface $conn */ +//$conn = $resource->getConnection(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION); +//$conn->update( +// $resource->getTableName('admin_user_expiration'), +// ['expires_at' => $pastDate->format('Y-m-d H:i:s')], +// ['user_id = ?' => (int)$userModelExpired->getId()] +//); From 071f9939233a6988503e3fe097e57d9cd00e47e0 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 30 Jun 2019 16:11:26 -0400 Subject: [PATCH 21/70] Move events out of adminhtml area; use Auth session prolong plugin instead of AdminSessionsManager to check for expired users (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/AdminSessionsManager.php | 46 +----------- .../Security/Model/Plugin/AuthSession.php | 16 +++- .../Security/Model/UserExpirationManager.php | 55 +------------- .../Unit/Model/Plugin/AuthSessionTest.php | 74 ++++++++++++++++++- .../Security/etc/{adminhtml => }/events.xml | 3 +- .../Model/AdminTokenServiceTest.php | 1 - .../Model/AdminSessionsManagerTest.php | 27 +------ .../Security/Model/Plugin/AuthSessionTest.php | 35 +++++++++ .../Model/UserExpirationManagerTest.php | 4 - .../Observer/AfterAdminUserLoadTest.php | 4 +- .../Observer/AfterAdminUserSaveTest.php | 2 - .../BeforeAdminUserAuthenticateTest.php | 1 - 12 files changed, 131 insertions(+), 137 deletions(-) rename app/code/Magento/Security/etc/{adminhtml => }/events.xml (82%) diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 0c1cca617cbc8..7503fe04ba480 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -75,12 +75,6 @@ class AdminSessionsManager */ private $maxIntervalBetweenConsecutiveProlongs = 60; - /** - * TODO: make sure we need this here - * @var UserExpirationManager - */ - private $userExpirationManager; - /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession @@ -88,7 +82,6 @@ class AdminSessionsManager * @param CollectionFactory $adminSessionInfoCollectionFactory * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param RemoteAddress $remoteAddress - * @param UserExpirationManager|null $userExpirationManager */ public function __construct( ConfigInterface $securityConfig, @@ -96,8 +89,7 @@ public function __construct( \Magento\Security\Model\AdminSessionInfoFactory $adminSessionInfoFactory, \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionInfoCollectionFactory, \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - RemoteAddress $remoteAddress, - \Magento\Security\Model\UserExpirationManager $userExpirationManager = null + RemoteAddress $remoteAddress ) { $this->securityConfig = $securityConfig; $this->authSession = $authSession; @@ -105,13 +97,10 @@ public function __construct( $this->adminSessionInfoCollectionFactory = $adminSessionInfoCollectionFactory; $this->dateTime = $dateTime; $this->remoteAddress = $remoteAddress; - $this->userExpirationManager = $userExpirationManager ?: - \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Security\Model\UserExpirationManager::class); } /** - * Handle all others active sessions according Sharing Account Setting and expired users. + * Handle all others active sessions according Sharing Account Setting * * @return $this * @since 100.1.0 @@ -144,33 +133,7 @@ public function processLogin() */ public function processProlong() { - // TODO: is this the right place for this? Or should I use a plugin? This method is called in a plugin - // also, don't want to hit the database every single time. We could put it within the lastProlongIsOldEnough - // in order to reduece database loads, but what if the user is expired already? How granular do we want to get? - // if their session is expired, then they will get logged out anyways, and we can handle deactivating them - // upon login or via the cron - - // already (\Magento\Security\Model\Plugin\AuthSession::aroundProlong, which plugs into - // \Magento\Backend\Model\Auth\Session::prolong, which is called from - // \Magento\Backend\App\Action\Plugin\Authentication::aroundDispatch, which is a plugin to - // \Magento\Backend\App\AbstractAction::dispatch) - - // \Magento\Backend\App\AbstractAction::dispatch is called, which kicks off the around plugin - // \Magento\Backend\App\Action\Plugin\Authentication::aroundDispatch, which calls - // \Magento\Backend\Model\Auth\Session::prolong, which kicks off the around plugin - // \Magento\Security\Model\Plugin\AuthSession::aroundProlong, which calls - // this method. - - // this method will prolong the session only if it's old enough, otherwise it's not called. -// if ($this->userExpirationManager->userIsExpired($this->authSession->getUser())) { -// $this->userExpirationManager->deactivateExpiredUsers([$this->authSession->getUser()->getId()]); -// } - if ($this->lastProlongIsOldEnough()) { - // TODO: throw exception? - if ($this->userExpirationManager->userIsExpired($this->authSession->getUser())) { - $this->userExpirationManager->deactivateExpiredUsers([$this->authSession->getUser()->getId()]); - } $this->getCurrentSession()->setData( 'updated_at', date( @@ -342,8 +305,6 @@ protected function createNewSession() } /** - * Create admin session info collection. - * * @return \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection * @since 100.1.0 */ @@ -354,7 +315,8 @@ protected function createAdminSessionInfoCollection() /** * Calculates diff between now and last session updated_at - * and decides whether new prolong must be triggered or not. + * and decides whether new prolong must be triggered or not + * * This is done to limit amount of session prolongs and updates to database * within some period of time - X * X - is calculated in getIntervalBetweenConsecutiveProlongs() diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index 01203caaa31cd..007f0871f70d1 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -7,6 +7,7 @@ use Magento\Backend\Model\Auth\Session; use Magento\Security\Model\AdminSessionsManager; +use Magento\Security\Model\UserExpirationManager; /** * Magento\Backend\Model\Auth\Session decorator @@ -33,22 +34,32 @@ class AuthSession */ protected $securityCookie; + /** + * @var UserExpirationManager + */ + private $userExpirationManager; + /** * @param \Magento\Framework\App\RequestInterface $request * @param \Magento\Framework\Message\ManagerInterface $messageManager * @param AdminSessionsManager $sessionsManager * @param \Magento\Security\Model\SecurityCookie $securityCookie + * @param UserExpirationManager|null $userExpirationManager */ public function __construct( \Magento\Framework\App\RequestInterface $request, \Magento\Framework\Message\ManagerInterface $messageManager, AdminSessionsManager $sessionsManager, - \Magento\Security\Model\SecurityCookie $securityCookie + \Magento\Security\Model\SecurityCookie $securityCookie, + \Magento\Security\Model\UserExpirationManager $userExpirationManager = null ) { $this->request = $request; $this->messageManager = $messageManager; $this->sessionsManager = $sessionsManager; $this->securityCookie = $securityCookie; + $this->userExpirationManager = $userExpirationManager ?: + \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Security\Model\UserExpirationManager::class); } /** @@ -60,7 +71,8 @@ public function __construct( */ public function aroundProlong(Session $session, \Closure $proceed) { - if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus()) { + if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus() || + $this->userExpirationManager->userIsExpired($session->getUser())) { $session->destroy(); $this->addUserLogoutNotification(); return null; diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index bd10b55e1b36d..8204295e2ed23 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -71,24 +71,6 @@ public function __construct( $this->userCollectionFactory = $userCollectionFactory; } - /** - * Revoke admin tokens for expired users. - * TODO: any better way than looping? - * TODO: remove - * @param \Magento\User\Model\User $user - * @throws \Exception - */ - public function deactivateExpiredUser(\Magento\User\Model\User $user): void - { - $currentSessions = $this->getSessionsForUser($user); - $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) - ->save(); - $user - ->setIsActive(0) - ->save(); - // TODO: remove expires_at record from new table - } - /** * Deactivate expired user accounts and invalidate their sessions. * @@ -122,23 +104,6 @@ public function deactivateExpiredUsers(?array $userIds = null): void $users->setDataToAll('is_active', 0)->save(); } - /** - * Get sessions for the given user. - * TODO: remove - * @param \Magento\User\Model\User $user - * @return ResourceModel\AdminSessionInfo\Collection - */ - private function getSessionsForUser(\Magento\User\Model\User $user) - { - $collection = $this->adminSessionInfoCollectionFactory->create(); - $collection - ->filterByUser($user->getId(), \Magento\Security\Model\AdminSessionInfo::LOGGED_IN) - ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()) - ->loadData(); - - return $collection; - } - /** * Check if the given user is expired. * // TODO: check users expired an hour ago (timezone stuff) @@ -150,30 +115,12 @@ public function userIsExpired(\Magento\User\Model\User $user): bool $isExpired = false; $expiredRecord = $this->userExpirationCollectionFactory->create() ->addExpiredRecordsForUserFilter($user->getId()) - ->getFirstItem(); // expiresAt: 1561824907, current timestamp: 1561824932 + ->getFirstItem(); if ($expiredRecord && $expiredRecord->getId()) { - //$expiresAt = $this->dateTime->gmtTimestamp($expiredRecord->getExpiredAt()); $expiresAt = $this->dateTime->timestamp($expiredRecord->getExpiresAt()); $isExpired = $expiresAt < $this->dateTime->gmtTimestamp(); } return $isExpired; } - - /** - * Check if the current user is expired and, if so, revoke their admin token. - */ - // private function revokeExpiredAdminUser() - // { - // $expiresAt = $this->dateTime->gmtTimestamp($this->authSession->getUser()->getExpiresAt()); - // if ($expiresAt < $this->dateTime->gmtTimestamp()) { - // $currentSessions = $this->getSessionsForCurrentUser(); - // $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) - // ->save(); - // $this->authSession->getUser() - // ->setIsActive(0) - // ->setExpiresAt(null) - // ->save(); - // } - // } } diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index 0f7f590b71de4..19c845a66bd86 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -37,6 +37,12 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ protected $objectManager; + /**@var \Magento\Security\Model\UserExpirationManager */ + protected $userExpirationManagerMock; + + /**@var \Magento\User\Model\User */ + protected $userMock; + /** * Init mocks for tests * @return void @@ -61,20 +67,31 @@ public function setUp() $this->securityCookieMock = $this->createPartialMock(SecurityCookie::class, ['setLogoutReasonCookie']); - $this->authSessionMock = $this->createPartialMock(\Magento\Backend\Model\Auth\Session::class, ['destroy']); + $this->authSessionMock = $this->createPartialMock( + \Magento\Backend\Model\Auth\Session::class, + ['destroy', 'getUser'] + ); $this->currentSessionMock = $this->createPartialMock( \Magento\Security\Model\AdminSessionInfo::class, ['isLoggedInStatus', 'getStatus', 'isActive'] ); + $this->userExpirationManagerMock = $this->createPartialMock( + \Magento\Security\Model\UserExpirationManager::class, + ['userIsExpired'] + ); + + $this->userMock = $this->createMock(\Magento\User\Model\User::class); + $this->model = $this->objectManager->getObject( \Magento\Security\Model\Plugin\AuthSession::class, [ 'request' => $this->requestMock, 'messageManager' => $this->messageManagerMock, 'sessionsManager' => $this->adminSessionsManagerMock, - 'securityCookie' => $this->securityCookieMock + 'securityCookie' => $this->securityCookieMock, + 'userExpirationManager' => $this->userExpirationManagerMock, ] ); @@ -154,6 +171,50 @@ public function testAroundProlongSessionIsNotActiveAndIsAjaxRequest() $this->model->aroundProlong($this->authSessionMock, $proceed); } + /** + * @return void + */ + public function testAroundProlongSessionIsActiveUserIsExpired() + { + $result = 'result'; + $errorMessage = 'Error Message'; + + $proceed = function () use ($result) { + return $result; + }; + + $this->currentSessionMock->expects($this->once()) + ->method('isLoggedInStatus') + ->willReturn(true); + + $this->authSessionMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->userMock); + + $this->userExpirationManagerMock->expects($this->once()) + ->method('userIsExpired') + ->with($this->userMock) + ->willReturn(true); + + $this->authSessionMock->expects($this->once()) + ->method('destroy'); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('isAjax') + ->willReturn(false); + + $this->adminSessionsManagerMock->expects($this->once()) + ->method('getLogoutReasonMessage') + ->willReturn($errorMessage); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with($errorMessage); + + $this->model->aroundProlong($this->authSessionMock, $proceed); + } + /** * @return void */ @@ -168,6 +229,15 @@ public function testAroundProlongSessionIsActive() ->method('isLoggedInStatus') ->willReturn(true); + $this->authSessionMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->userMock); + + $this->userExpirationManagerMock->expects($this->once()) + ->method('userIsExpired') + ->with($this->userMock) + ->willReturn(false); + $this->adminSessionsManagerMock->expects($this->any()) ->method('processProlong'); diff --git a/app/code/Magento/Security/etc/adminhtml/events.xml b/app/code/Magento/Security/etc/events.xml similarity index 82% rename from app/code/Magento/Security/etc/adminhtml/events.xml rename to app/code/Magento/Security/etc/events.xml index 1bd8e9807c05f..640fcf7b3b20e 100644 --- a/app/code/Magento/Security/etc/adminhtml/events.xml +++ b/app/code/Magento/Security/etc/events.xml @@ -1,7 +1,6 @@ - @@ -9,6 +8,6 @@ - + diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php b/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php index 21a9f6d942dc3..68e049804e95a 100644 --- a/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php @@ -60,7 +60,6 @@ public function testCreateAdminAccessToken() } /** - * TODO: fix failing test * @magentoDataFixture Magento/Security/_files/expired_users.php * @expectedException \Magento\Framework\Exception\AuthenticationException */ diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php index 74cefb9d41a59..a201dbfdf1b03 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php @@ -3,16 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Security\Model; -/** - * Class AdminSessionsManagerTest - * TODO: test AdminSessionsManager::processLogin - * TODO: test AdminSessionsManager::processProlong - * - * @package Magento\Security\Model - */ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase { /** @@ -69,8 +61,8 @@ protected function setUp() protected function tearDown() { $this->auth = null; - $this->authSession = null; - $this->adminSessionInfo = null; + $this->authSession = null; + $this->adminSessionInfo = null; $this->adminSessionsManager = null; $this->objectManager = null; parent::tearDown(); @@ -133,23 +125,10 @@ public function testTerminateOtherSessionsProcessLogin() $session->load('669e2e3d752e8', 'session_id'); $this->assertEquals( AdminSessionInfo::LOGGED_OUT_BY_LOGIN, - (int)$session->getStatus() + (int) $session->getStatus() ); } - /** - * Test that expired users cannot login. - * - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedException \Magento\Framework\Exception\AuthenticationException - * @magentoDataFixture Magento/Security/_files/expired_users.php - * @magentoDbIsolation enabled - */ - public function testExpiredUserProcessLogin() - { - static::markTestSkipped('to implement'); - } - /** * Test if current session is retrieved * diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 52268dc96d8a3..a112ba19a6008 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -140,4 +140,39 @@ public function testProcessProlong() $this->assertGreaterThan(strtotime($oldUpdatedAt), strtotime($updatedAt)); } + + /** + */ + public function testProcessProlongWithExpiredUser() + { + $this->auth->login( + \Magento\TestFramework\Bootstrap::ADMIN_NAME, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + + $expireDate = new \DateTime(); + $expireDate->modify('-10 days'); + $user = $this->objectManager->create(\Magento\User\Model\User::class); + $user->loadByUsername(\Magento\TestFramework\Bootstrap::ADMIN_NAME); + $userExpirationFactory = $this->objectManager->create(\Magento\Security\Model\UserExpirationFactory::class); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $userExpirationFactory->create(); + $userExpiration->setId($user->getId()) + ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) + ->save(); + + // need to trigger a prolong + $sessionId = $this->authSession->getSessionId(); + $prolongsDiff = 4 * log($this->securityConfig->getAdminSessionLifetime()) + 2; + $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - $prolongsDiff); + $this->adminSessionsManager->getCurrentSession() + ->setData( + 'updated_at', + $dateInPast + ) + ->save(); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $this->authSession->prolong(); + static::assertFalse($this->auth->isLoggedIn()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 1840748444842..617837c9eaaf2 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -11,11 +11,7 @@ /** * TODO: test logging out sessions - * TODO: test that you cannot create a token for an expired user (where would I put this test?) - * TODO: test AdminSessionsManager::processLogin - * TODO: test AdminSessionsManager::processProlong (do it here or in the AdminSessionsManagerTest?) * - * @magentoAppArea adminhtml */ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php index 0fcd90913e562..37c2173f675ae 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php @@ -10,9 +10,7 @@ use Magento\TestFramework\Helper\Bootstrap; /** - * Test for \Magento\Security\Observer\AfterAdminUserLoad - * - * @magentoAppArea adminhtml + * Test for \Magento\Security\Observer\AfterAdminUserLoad * */ class AfterAdminUserLoadTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php index 6032b651b4dc3..75b2e1ca2fc32 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php @@ -11,8 +11,6 @@ /** * Test for \Magento\Security\Observer\AfterAdminUserSave - * - * @magentoAppArea adminhtml */ class AfterAdminUserSaveTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php index db7a83877d1b2..9a8f66eb9ef65 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php @@ -11,7 +11,6 @@ /** * Test for \Magento\Security\Observer\BeforeAdminUserAuthenticate - * @magentoAppArea adminhtml */ class BeforeAdminUserAuthenticateTest extends \PHPUnit\Framework\TestCase { From b407c8cbd87fdc04690d08962e7ceeb7be3dd620 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 30 Jun 2019 19:22:31 -0400 Subject: [PATCH 22/70] fix crontab definition, copyright (#22833: Short-term admin accounts) --- .../Test/Unit/Model/AdminSessionsManagerTest.php | 15 ++++----------- app/code/Magento/Security/etc/crontab.xml | 2 +- app/code/Magento/Security/etc/events.xml | 6 ++++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index a062cad9d61da..be0bdaecf8de3 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -50,7 +50,7 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ protected $objectManager; - /** + /* * @var RemoteAddress */ protected $remoteAddressMock; @@ -91,9 +91,7 @@ public function setUp() ['create'] ); - $this->currentSessionMock = $this->createPartialMock( - \Magento\Security\Model\AdminSessionInfo::class, - [ + $this->currentSessionMock = $this->createPartialMock(\Magento\Security\Model\AdminSessionInfo::class, [ 'isActive', 'getStatus', 'load', @@ -103,8 +101,7 @@ public function setUp() 'getUserId', 'getSessionId', 'getUpdatedAt' - ] - ); + ]); $this->securityConfigMock = $this->getMockBuilder(\Magento\Security\Model\ConfigInterface::class) ->disableOriginalConstructor() @@ -154,7 +151,7 @@ public function testProcessLogin() ->method('getSessionId') ->willReturn($sessionId); - $this->authSessionMock->expects($this->any()) + $this->authSessionMock->expects($this->once()) ->method('getUser') ->willReturn($this->userMock); $this->userMock->expects($this->once()) @@ -256,10 +253,6 @@ public function testProcessProlong() ->method('save') ->willReturnSelf(); - $this->authSessionMock->expects($this->once()) - ->method('getUser') - ->willReturn($this->userMock); - $this->model->processProlong(); } diff --git a/app/code/Magento/Security/etc/crontab.xml b/app/code/Magento/Security/etc/crontab.xml index 203cf5c2a2389..5944ab41fc247 100644 --- a/app/code/Magento/Security/etc/crontab.xml +++ b/app/code/Magento/Security/etc/crontab.xml @@ -13,7 +13,7 @@ 0 0 * * * - + 0 * * * * diff --git a/app/code/Magento/Security/etc/events.xml b/app/code/Magento/Security/etc/events.xml index 640fcf7b3b20e..63c35d5c31a6f 100644 --- a/app/code/Magento/Security/etc/events.xml +++ b/app/code/Magento/Security/etc/events.xml @@ -1,4 +1,10 @@ + From 47a9ed7db12ca908bf67ef0d4af17eb91addd19d Mon Sep 17 00:00:00 2001 From: lfolco Date: Thu, 4 Jul 2019 12:56:32 -0400 Subject: [PATCH 23/70] Refactor observers, update tests (magento/magento2#22833: Short-term admin accounts) --- .../Model/Plugin/UserValidationRules.php | 2 + .../Model/ResourceModel/UserExpiration.php | 2 + .../UserExpiration/Collection.php | 11 +- .../Security/Model/UserExpirationManager.php | 3 + ...te.php => AdminUserAuthenticateBefore.php} | 12 +- .../Security/Observer/AfterAdminUserLoad.php | 8 + .../Security/Observer/AfterAdminUserSave.php | 8 + .../BeforeAdminUserAuthenticateTest.php | 6 +- app/code/Magento/Security/etc/crontab.xml | 2 +- app/code/Magento/Security/etc/events.xml | 2 +- .../Security/Model/Plugin/AuthSessionTest.php | 3 + .../UserExpiration/CollectionTest.php | 22 ++- .../Model/UserExpirationManagerTest.php | 185 +++++++++++------- ...hp => AdminUserAuthenticateBeforeTest.php} | 4 +- .../Observer/AfterAdminUserLoadTest.php | 1 - .../Magento/Security/_files/expired_users.php | 13 -- 16 files changed, 185 insertions(+), 99 deletions(-) rename app/code/Magento/Security/Observer/{BeforeAdminUserAuthenticate.php => AdminUserAuthenticateBefore.php} (83%) rename dev/tests/integration/testsuite/Magento/Security/Observer/{BeforeAdminUserAuthenticateTest.php => AdminUserAuthenticateBeforeTest.php} (92%) diff --git a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php index 75b826dfb6b65..f400d962644d6 100644 --- a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php +++ b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php @@ -28,6 +28,8 @@ public function __construct(\Magento\Security\Model\UserExpiration\Validator $va } /** + * Add the Expires At validator to user validation rules. + * * @param \Magento\User\Model\UserValidationRules $userValidationRules * @param \Magento\Framework\Validator\DataObject $result * @return \Magento\Framework\Validator\DataObject diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index 5afca619c3f7f..5702b6ce65725 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -21,6 +21,8 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD protected $_isPkAutoIncrement = false; /** + * Define main table + * * @return void */ protected function _construct() diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php index 08c72f7d9fd2f..dee92c599f4de 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php @@ -18,6 +18,8 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab protected $_idFieldName = 'user_id'; /** + * Initialize collection + * * @return void */ protected function _construct() @@ -53,7 +55,8 @@ public function addActiveExpiredUsersFilter($now = null): Collection /** * Filter collection by user id. - * @param array $userIds + * + * @param int[] $userIds * @return Collection */ public function addUserIdsFilter($userIds = []): Collection @@ -64,12 +67,12 @@ public function addUserIdsFilter($userIds = []): Collection /** * Get any expired records for the given user. * - * @param $userId + * @param string $userId * @return Collection */ - public function addExpiredRecordsForUserFilter($userId): Collection + public function addExpiredRecordsForUserFilter(string $userId): Collection { return $this->addActiveExpiredUsersFilter() - ->addFieldToFilter('main_table.user_id', $userId); + ->addFieldToFilter('main_table.user_id', (int)$userId); } } diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index 8204295e2ed23..8d0bab3eefa5f 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -90,6 +90,7 @@ public function deactivateExpiredUsers(?array $userIds = null): void $currentSessions = $this->adminSessionInfoCollectionFactory->create() ->addFieldToFilter('user_id', ['in' => $expiredRecords->getAllIds()]) ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()); + /** @var \Magento\Security\Model\AdminSessionInfo $currentSession */ $currentSessions->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT) ->save(); } @@ -107,12 +108,14 @@ public function deactivateExpiredUsers(?array $userIds = null): void /** * Check if the given user is expired. * // TODO: check users expired an hour ago (timezone stuff) + * * @param \Magento\User\Model\User $user * @return bool */ public function userIsExpired(\Magento\User\Model\User $user): bool { $isExpired = false; + /** @var \Magento\Security\Model\UserExpiration $expiredRecord */ $expiredRecord = $this->userExpirationCollectionFactory->create() ->addExpiredRecordsForUserFilter($user->getId()) ->getFirstItem(); diff --git a/app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php similarity index 83% rename from app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php rename to app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index 1c888ec511646..0aa9e2566a789 100644 --- a/app/code/Magento/Security/Observer/BeforeAdminUserAuthenticate.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -13,11 +13,11 @@ use Magento\Security\Model\UserExpirationManager; /** - * Class BeforeAdminUserAuthenticate + * Check for expired users. * * @package Magento\Security\Observer */ -class BeforeAdminUserAuthenticate implements ObserverInterface +class AdminUserAuthenticateBefore implements ObserverInterface { /** * @var UserExpirationManager @@ -29,6 +29,12 @@ class BeforeAdminUserAuthenticate implements ObserverInterface */ private $user; + /** + * AdminUserAuthenticateBefore constructor. + * + * @param UserExpirationManager $userExpirationManager + * @param \Magento\User\Model\User $user + */ public function __construct( \Magento\Security\Model\UserExpirationManager $userExpirationManager, \Magento\User\Model\User $user @@ -38,6 +44,8 @@ public function __construct( } /** + * Check for expired user when logging in. + * * @param Observer $observer * @return void * @throws AuthenticationException diff --git a/app/code/Magento/Security/Observer/AfterAdminUserLoad.php b/app/code/Magento/Security/Observer/AfterAdminUserLoad.php index 4bc6804276c5c..133b26c2c01a2 100644 --- a/app/code/Magento/Security/Observer/AfterAdminUserLoad.php +++ b/app/code/Magento/Security/Observer/AfterAdminUserLoad.php @@ -25,6 +25,12 @@ class AfterAdminUserLoad implements ObserverInterface */ private $userExpirationResource; + /** + * AfterAdminUserLoad constructor. + * + * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory + * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + */ public function __construct( \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource @@ -35,6 +41,8 @@ public function __construct( } /** + * Set the user expiration date onto the user. + * * @param Observer $observer * @return void */ diff --git a/app/code/Magento/Security/Observer/AfterAdminUserSave.php b/app/code/Magento/Security/Observer/AfterAdminUserSave.php index 37fb6af3ccaa6..247e88a720ceb 100644 --- a/app/code/Magento/Security/Observer/AfterAdminUserSave.php +++ b/app/code/Magento/Security/Observer/AfterAdminUserSave.php @@ -25,6 +25,12 @@ class AfterAdminUserSave implements ObserverInterface */ private $userExpirationResource; + /** + * AfterAdminUserSave constructor. + * + * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory + * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + */ public function __construct( \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource @@ -35,6 +41,8 @@ public function __construct( } /** + * Save user expiration. + * * @param Observer $observer * @return void */ diff --git a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php index 9df2a133ff2a9..263d7b5359b29 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php @@ -8,7 +8,7 @@ namespace Magento\Security\Test\Unit\Observer; /** - * Test for \Magento\Security\Observer\BeforeAdminUserAuthenticate + * Test for \Magento\Security\Observer\AdminUserAuthenticateBefore * * @package Magento\Security\Test\Unit\Observer */ @@ -30,7 +30,7 @@ class BeforeAdminUserAuthenticateTest extends \PHPUnit\Framework\TestCase private $userMock; /** - * @var \Magento\Security\Observer\BeforeAdminUserAuthenticate + * @var \Magento\Security\Observer\AdminUserAuthenticateBefore */ private $observer; @@ -64,7 +64,7 @@ protected function setUp() ); $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['loadByUsername', 'getId']); $this->observer = $this->objectManager->getObject( - \Magento\Security\Observer\BeforeAdminUserAuthenticate::class, + \Magento\Security\Observer\AdminUserAuthenticateBefore::class, [ 'userExpirationManager' => $this->userExpirationManagerMock, 'user' => $this->userMock, diff --git a/app/code/Magento/Security/etc/crontab.xml b/app/code/Magento/Security/etc/crontab.xml index 5944ab41fc247..7ee046ee44850 100644 --- a/app/code/Magento/Security/etc/crontab.xml +++ b/app/code/Magento/Security/etc/crontab.xml @@ -13,7 +13,7 @@ 0 0 * * * - + 0 * * * * diff --git a/app/code/Magento/Security/etc/events.xml b/app/code/Magento/Security/etc/events.xml index 63c35d5c31a6f..9db9a67947ca8 100644 --- a/app/code/Magento/Security/etc/events.xml +++ b/app/code/Magento/Security/etc/events.xml @@ -14,6 +14,6 @@ - + diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index a112ba19a6008..1d86c40018659 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -142,6 +142,9 @@ public function testProcessProlong() } /** + * Test processing prolong with an expired user. + * + * @magentoDbIsolation enabled */ public function testProcessProlongWithExpiredUser() { diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php index 63f1c377f99cc..b78ed9dc82377 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php @@ -38,18 +38,32 @@ protected function setUp() /** * @magentoDataFixture Magento/Security/_files/expired_users.php */ - public function testExpiredActiveUsersFilter() + public function testAddExpiredActiveUsersFilter() { /** @var \Magento\Security\Model\ResourceModel\UserExpiration\Collection $collectionModel */ $collectionModel = $this->collectionModelFactory->create(); $collectionModel->addActiveExpiredUsersFilter(); - static::assertGreaterThanOrEqual(1, $collectionModel->getSize()); + static::assertEquals(1, $collectionModel->getSize()); } /** * @magentoDataFixture Magento/Security/_files/expired_users.php */ - public function testGetExpiredRecordsForUser() + public function testAddUserIdsFilter() + { + $adminUserNameFromFixture = 'adminUserExpired'; + $user = $this->objectManager->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + + /** @var \Magento\Security\Model\ResourceModel\UserExpiration\Collection $collectionModel */ + $collectionModel = $this->collectionModelFactory->create()->addUserIdsFilter([$user->getId()]); + static::assertEquals(1, $collectionModel->getSize()); + } + + /** + * @magentoDataFixture Magento/Security/_files/expired_users.php + */ + public function testAddExpiredRecordsForUserFilter() { $adminUserNameFromFixture = 'adminUserExpired'; $user = $this->objectManager->create(\Magento\User\Model\User::class); @@ -57,6 +71,6 @@ public function testGetExpiredRecordsForUser() /** @var \Magento\Security\Model\ResourceModel\UserExpiration\Collection $collectionModel */ $collectionModel = $this->collectionModelFactory->create()->addExpiredRecordsForUserFilter($user->getId()); - static::assertGreaterThanOrEqual(1, $collectionModel->getSize()); + static::assertEquals(1, $collectionModel->getSize()); } } diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 617837c9eaaf2..e07c539d2b49a 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -7,26 +7,70 @@ namespace Magento\Security\Model; -use Magento\TestFramework\Helper\Bootstrap; - /** * TODO: test logging out sessions - * */ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase { + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\Backend\Model\Auth + */ + private $auth; + + /** + * @var \Magento\Backend\Model\Auth\Session + */ + private $authSession; + + /** + * @var \Magento\Security\Model\AdminSessionInfo + */ + private $adminSessionInfo; + + /** + * @var \Magento\Security\Model\UserExpirationManager + */ + private $userExpirationManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager->get(\Magento\Framework\Config\ScopeInterface::class) + ->setCurrentScope(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); + $this->auth = $this->objectManager->create(\Magento\Backend\Model\Auth::class); + $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); + $this->auth->setAuthStorage($this->authSession); + $this->adminSessionInfo = $this->objectManager->create(\Magento\Security\Model\AdminSessionInfo::class); + $this->userExpirationManager = + $this->objectManager->create(\Magento\Security\Model\UserExpirationManager::class); + } + + /** + * Tear down + */ + protected function tearDown() + { + $this->auth = null; + $this->authSession = null; + $this->adminSessionInfo = null; + $this->userExpirationManager = null; + $this->objectManager = null; + } /** * @magentoDataFixture Magento/Security/_files/expired_users.php */ public function testUserIsExpired() { + static::markTestSkipped(); $adminUserNameFromFixture = 'adminUserExpired'; - $user = $this->getUserFromUserName($adminUserNameFromFixture); - /** @var \Magento\Security\Model\UserExpirationManager $userExpirationManager */ - $userExpirationManager = Bootstrap::getObjectManager() - ->create(\Magento\Security\Model\UserExpirationManager::class); - static::assertTrue($userExpirationManager->userIsExpired($user)); + $user = $this->loadUserByUsername($adminUserNameFromFixture); + static::assertTrue($this->userExpirationManager->userIsExpired($user)); } /** @@ -34,13 +78,19 @@ public function testUserIsExpired() */ public function testDeactivateExpiredUsersWithExpiredUser() { - $adminUserNameFromFixture = 'adminUserExpired'; - - list($user, $token, $expiredUserModel) = $this->setupCronTests($adminUserNameFromFixture); - + $adminUsernameFromFixture = 'adminUserNotExpired'; + $this->loginUser($adminUsernameFromFixture); + $user = $this->loadUserByUsername($adminUsernameFromFixture); + $sessionId = $this->authSession->getSessionId(); + $this->expireUser($user); + $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $user->reload(); + $userExpirationModel = $this->loadExpiredUserModelByUser($user); static::assertEquals(0, $user->getIsActive()); - static::assertEquals(null, $token->getId()); - static::assertNull($expiredUserModel->getId()); + static::assertNull($userExpirationModel->getId()); + static::assertEquals(AdminSessionInfo::LOGGED_OUT, (int)$this->adminSessionInfo->getStatus()); + $this->auth->logout(); } /** @@ -48,96 +98,95 @@ public function testDeactivateExpiredUsersWithExpiredUser() */ public function testDeactivateExpiredUsersWithNonExpiredUser() { - $adminUserNameFromFixture = 'adminUserNotExpired'; - // log them in - $adminToken = $this->createToken($adminUserNameFromFixture); - - list($user, $token, $expiredUserModel) = $this->setupCronTests($adminUserNameFromFixture); - + // TODO: login fails for the second test that tries to log a user in, doesn't matter which test + // it's trying to create a session for the user ID in the previous test + $adminUsernameFromFixture = 'adminUserNotExpired'; + $this->loginUser($adminUsernameFromFixture); + $user = $this->loadUserByUsername($adminUsernameFromFixture); + $sessionId = $this->authSession->getSessionId(); + $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + $user->reload(); + $userExpirationModel = $this->loadExpiredUserModelByUser($user); + $this->adminSessionInfo->load($sessionId, 'session_id'); static::assertEquals(1, $user->getIsActive()); - static::assertNotNull($token->getId()); - static::assertEquals($expiredUserModel->getUserId(), $user->getId()); + static::assertEquals($user->getId(), $userExpirationModel->getId()); + static::assertEquals(AdminSessionInfo::LOGGED_IN, (int)$this->adminSessionInfo->getStatus()); + $this->auth->logout(); } /** - * @param string $adminUserNameFromFixture - * @return array + * Test deactivating without inputting a user. + * + * @magentoDataFixture Magento/Security/_files/expired_users.php */ - private function setupCronTests(string $adminUserNameFromFixture): array + public function testDeactivateExpiredUsers() { - // TODO: set the user expired after calling this - // TODO: use this to test the observer with exception: - // Magento\Framework\Exception\Plugin\AuthenticationException : The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later. - //$adminToken = $this->createToken($adminUserNameFromFixture); // TODO: this logs the user in, which kicks off the deactivate call - - /** @var \Magento\Security\Model\UserExpirationManager $job */ - $userExpirationManager = Bootstrap::getObjectManager() - ->create(\Magento\Security\Model\UserExpirationManager::class); - $userExpirationManager->deactivateExpiredUsers(); - - /** @var \Magento\User\Model\User $user */ - $user = $this->getUserFromUserName($adminUserNameFromFixture); - - // this is for the API only - $oauthToken = $this->getOauthTokenByUser($user); - $expiredUserModel = $this->getExpiredUserModelByUser($user); - - return [$user, $oauthToken, $expiredUserModel]; + static::markTestSkipped(); + $notExpiredUser = $this->loadUserByUsername('adminUserNotExpired'); + $expiredUser = $this->loadUserByUsername('adminUserExpired'); + $this->userExpirationManager->deactivateExpiredUsers(); + $notExpiredUserExpirationModel = $this->loadExpiredUserModelByUser($notExpiredUser); + $expiredUserExpirationModel = $this->loadExpiredUserModelByUser($expiredUser); + + static::assertNotNull($notExpiredUserExpirationModel->getId()); + static::assertNull($expiredUserExpirationModel->getId()); + $notExpiredUser->reload(); + $expiredUser->reload(); + static::assertEquals($notExpiredUser->getIsActive(), 1); + static::assertEquals($expiredUser->getIsActive(), 0); } /** - * TODO: this calls user->login and throws an AuthenticationException + * Login the given user and return a user model. * - * @param string $adminUserNameFromFixture - * @return string + * @param string $username * @throws \Magento\Framework\Exception\AuthenticationException - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\LocalizedException */ - private function createToken(string $adminUserNameFromFixture): string + private function loginUser(string $username) { - /** @var \Magento\Integration\Api\AdminTokenServiceInterface $tokenService */ - $tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Api\AdminTokenServiceInterface::class); - $token = $tokenService->createAdminAccessToken( - $adminUserNameFromFixture, + $this->auth->login( + $username, \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD ); - - return $token; } /** - * @param string $adminUserNameFromFixture + * @param $username * @return \Magento\User\Model\User */ - private function getUserFromUserName(string $adminUserNameFromFixture): \Magento\User\Model\User + private function loadUserByUsername(string $username): \Magento\User\Model\User { /** @var \Magento\User\Model\User $user */ - $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername($adminUserNameFromFixture); + $user = $this->objectManager->create(\Magento\User\Model\User::class); + $user->loadByUsername($username); return $user; } /** + * Expire the given user and return the UserExpiration model. + * * @param \Magento\User\Model\User $user - * @return \Magento\Integration\Model\Oauth\Token + * @throws \Exception */ - private function getOauthTokenByUser(\Magento\User\Model\User $user): \Magento\Integration\Model\Oauth\Token + private function expireUser(\Magento\User\Model\User $user) { - /** @var \Magento\Integration\Model\Oauth\Token $tokenModel */ - $tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); - $oauthToken = $tokenModel->loadByAdminId($user->getId()); - return $oauthToken; + $expireDate = new \DateTime(); + $expireDate->modify('-10 days'); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $this->objectManager->create(\Magento\Security\Model\UserExpiration::class); + $userExpiration->setId($user->getId()) + ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) + ->save(); } /** * @param \Magento\User\Model\User $user - * @return UserExpiration + * @return \Magento\Security\Model\UserExpiration */ - private function getExpiredUserModelByUser(\Magento\User\Model\User $user): \Magento\Security\Model\UserExpiration + private function loadExpiredUserModelByUser(\Magento\User\Model\User $user): \Magento\Security\Model\UserExpiration { /** @var \Magento\Security\Model\UserExpiration $expiredUserModel */ - $expiredUserModel = Bootstrap::getObjectManager()->get(\Magento\Security\Model\UserExpiration::class); + $expiredUserModel = $this->objectManager->create(\Magento\Security\Model\UserExpiration::class); $expiredUserModel->load($user->getId()); return $expiredUserModel; } diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AdminUserAuthenticateBeforeTest.php similarity index 92% rename from dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php rename to dev/tests/integration/testsuite/Magento/Security/Observer/AdminUserAuthenticateBeforeTest.php index 9a8f66eb9ef65..f25b836b80514 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/BeforeAdminUserAuthenticateTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AdminUserAuthenticateBeforeTest.php @@ -10,9 +10,9 @@ use Magento\TestFramework\Helper\Bootstrap; /** - * Test for \Magento\Security\Observer\BeforeAdminUserAuthenticate + * Test for \Magento\Security\Observer\AdminUserAuthenticateBefore */ -class BeforeAdminUserAuthenticateTest extends \PHPUnit\Framework\TestCase +class AdminUserAuthenticateBeforeTest extends \PHPUnit\Framework\TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php index 37c2173f675ae..fa552f04b9f2e 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php @@ -43,5 +43,4 @@ public function testWithNonExpiredUser() $loadedUser->load($userId); static::assertNull($loadedUser->getExpiresAt()); } - } diff --git a/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php b/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php index 5a3971cd9d0bb..ac45f120ca6d3 100644 --- a/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php +++ b/dev/tests/integration/testsuite/Magento/Security/_files/expired_users.php @@ -56,16 +56,3 @@ ->setId($userModelExpired->getId()) ->setExpiresAt($pastDate->format('Y-m-d H:i:s')) ->save(); - -// TODO: remove -// need to bypass model validation to set expired date -//$pastDate = new \DateTime(); -//$pastDate->modify('-10 days'); -//$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); -///** @var \Magento\Framework\DB\Adapter\AdapterInterface $conn */ -//$conn = $resource->getConnection(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION); -//$conn->update( -// $resource->getTableName('admin_user_expiration'), -// ['expires_at' => $pastDate->format('Y-m-d H:i:s')], -// ['user_id = ?' => (int)$userModelExpired->getId()] -//); From 6407af86689bd905d1fb98d441147420e7f9ec6e Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 6 Jul 2019 13:51:30 -0400 Subject: [PATCH 24/70] handle dates with store time (magento/magento2#22833: Short-term admin accounts) --- .../Model/ResourceModel/UserExpiration.php | 60 ++++++++++++++++--- .../Magento/Security/Model/UserExpiration.php | 37 +----------- .../Model/UserExpiration/Validator.php | 14 ++++- .../Security/Model/UserExpirationManager.php | 3 +- .../Observer/AdminUserAuthenticateBefore.php | 2 +- .../Magento/User/Block/User/Edit/Tab/Main.php | 12 +++- .../Model/UserExpirationManagerTest.php | 4 +- 7 files changed, 76 insertions(+), 56 deletions(-) diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index 5702b6ce65725..21ee2bc077a2b 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -20,6 +20,35 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD */ protected $_isPkAutoIncrement = false; + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + private $timezone; + + /** + * @var \Magento\Framework\Stdlib\DateTime\DateTime + */ + private $dateTime; + + /** + * UserExpiration constructor. + * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param null $connectionName + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, + $connectionName = null + ) { + parent::__construct($context, $connectionName); + $this->timezone = $timezone; + $this->dateTime = $dateTime; + } + /** * Define main table * @@ -31,19 +60,34 @@ protected function _construct() } /** - * Perform actions before object save + * Convert to UTC time. * - * @param \Magento\Framework\Model\AbstractModel $object + * @param \Magento\Framework\Model\AbstractModel $userExpiration * @return $this - * @throws \Magento\Framework\Exception\LocalizedException */ - public function _beforeSave(\Magento\Framework\Model\AbstractModel $object) + protected function _beforeSave(\Magento\Framework\Model\AbstractModel $userExpiration) { - /** @var $object \Magento\Security\Model\UserExpiration */ - if ($object->getExpiresAt() instanceof \DateTimeInterface) { + /** @var $userExpiration \Magento\Security\Model\UserExpiration */ + $expiresAt = $userExpiration->getExpiresAt(); + $utcValue = $this->dateTime->gmtDate('Y-m-d H:i:s', $expiresAt); + $userExpiration->setExpiresAt($utcValue); + + return $this; + } - // TODO: use this? need to check if we're ever passing in a \DateTimeInterface or if it's always a string - $object->setExpiresAt($object->getExpiresAt()->format('Y-m-d H:i:s')); + /** + * Convert to store time. + * + * @param \Magento\Framework\Model\AbstractModel $userExpiration + * @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @throws \Exception + */ + protected function _afterLoad(\Magento\Framework\Model\AbstractModel $userExpiration) + { + /** @var $userExpiration \Magento\Security\Model\UserExpiration */ + if ($userExpiration->getExpiresAt()) { + $storeValue = $this->timezone->date($userExpiration->getExpiresAt()); + $userExpiration->setExpiresAt($storeValue); } return $this; diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index eaf2259375f0a..0e70243f30117 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -10,38 +10,11 @@ /** * Admin User Expiration model. * @method string getExpiresAt() - * @method \Magento\Security\Model\UserExpiration setExpiresAt(string $value) + * @method \Magento\Security\Model\UserExpiration setExpiresAt($value) */ class UserExpiration extends \Magento\Framework\Model\AbstractModel { - /** - * @var UserExpiration\Validator - */ - private $validator; - - /** - * UserExpiration constructor. - * - * @param \Magento\Framework\Model\Context $context - * @param \Magento\Framework\Registry $registry - * @param UserExpiration\Validator $validator - * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource - * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection - * @param array $data - */ - public function __construct( - \Magento\Framework\Model\Context $context, - \Magento\Framework\Registry $registry, - \Magento\Security\Model\UserExpiration\Validator $validator, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] - ) { - parent::__construct($context, $registry, $resource, $resourceCollection, $data); - $this->validator = $validator; - } - /** * Resource initialization * @@ -51,12 +24,4 @@ protected function _construct() { $this->_init(\Magento\Security\Model\ResourceModel\UserExpiration::class); } - - /** - * TODO: remove and use a plugin on UserValidationRules - */ -// protected function _getValidationRulesBeforeSave() -// { -// return $this->validator; -// } } diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index 4b82f2ab43f07..20fca2215a899 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -16,6 +16,15 @@ */ class Validator extends AbstractValidator { + /**@var \Magento\Framework\Stdlib\DateTime\DateTime */ + private $dateTime; + + public function __construct( + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + ) { + + $this->dateTime = $dateTime; + } /** * Ensure that the given date is later than the current date. @@ -31,9 +40,8 @@ public function isValid($value) $expiresAt = $value; $label = 'Expiration date'; if (\Zend_Validate::is($expiresAt, 'NotEmpty')) { - $currentTime = new \DateTime(); - $expiresAt = new \DateTime($expiresAt); - + $currentTime = $this->dateTime->gmtTimestamp(); + $expiresAt = $this->dateTime->gmtTimestamp($value); if ($expiresAt < $currentTime) { $messages['expires_at'] = __('"%1" must be later than the current date.', $label); } diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index 8d0bab3eefa5f..44e8226e391ed 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -10,7 +10,7 @@ use Magento\Security\Model\ResourceModel\UserExpiration\Collection as ExpiredUsersCollection; /** - * Class to handle expired admin users. + * Class to handle admin user expirations. */ class UserExpirationManager { @@ -107,7 +107,6 @@ public function deactivateExpiredUsers(?array $userIds = null): void /** * Check if the given user is expired. - * // TODO: check users expired an hour ago (timezone stuff) * * @param \Magento\User\Model\User $user * @return bool diff --git a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index 0aa9e2566a789..d40f25a023bbc 100644 --- a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -56,7 +56,7 @@ public function execute(Observer $observer) /** @var \Magento\User\Model\User $user */ $user = $this->user->loadByUsername($username); - if ($this->userExpirationManager->userIsExpired($user)) { + if ($user->getId() && $this->userExpirationManager->userIsExpired($user)) { $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); throw new AuthenticationException( __( diff --git a/app/code/Magento/User/Block/User/Edit/Tab/Main.php b/app/code/Magento/User/Block/User/Edit/Tab/Main.php index 85ac6898a27c3..c92714ec1eb5a 100644 --- a/app/code/Magento/User/Block/User/Edit/Tab/Main.php +++ b/app/code/Magento/User/Block/User/Edit/Tab/Main.php @@ -169,7 +169,13 @@ protected function _prepareForm() ] ); } - // TODO: use store time and convert to GMT + + $dateFormat = $this->_localeDate->getDateFormat( + \IntlDateFormatter::MEDIUM + ); + $timeFormat = $this->_localeDate->getTimeFormat( + \IntlDateFormatter::MEDIUM + ); $baseFieldset->addField( 'expires_at', 'date', @@ -177,8 +183,8 @@ protected function _prepareForm() 'name' => 'expires_at', 'label' => __('Expiration Date'), 'title' => __('Expiration Date'), - 'date_format' => 'yyyy-MM-dd', - 'time_format' => 'hh:mm:ss', + 'date_format' => $dateFormat, + 'time_format' => $timeFormat, 'class' => 'validate-date', ] ); diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index e07c539d2b49a..a5142d041f0b0 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -44,8 +44,8 @@ protected function setUp() ->setCurrentScope(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); $this->auth = $this->objectManager->create(\Magento\Backend\Model\Auth::class); $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); - $this->auth->setAuthStorage($this->authSession); $this->adminSessionInfo = $this->objectManager->create(\Magento\Security\Model\AdminSessionInfo::class); + $this->auth->setAuthStorage($this->authSession); $this->userExpirationManager = $this->objectManager->create(\Magento\Security\Model\UserExpirationManager::class); } @@ -67,7 +67,6 @@ protected function tearDown() */ public function testUserIsExpired() { - static::markTestSkipped(); $adminUserNameFromFixture = 'adminUserExpired'; $user = $this->loadUserByUsername($adminUserNameFromFixture); static::assertTrue($this->userExpirationManager->userIsExpired($user)); @@ -121,7 +120,6 @@ public function testDeactivateExpiredUsersWithNonExpiredUser() */ public function testDeactivateExpiredUsers() { - static::markTestSkipped(); $notExpiredUser = $this->loadUserByUsername('adminUserNotExpired'); $expiredUser = $this->loadUserByUsername('adminUserExpired'); $this->userExpirationManager->deactivateExpiredUsers(); From 75664d49208141cdae4ccf976fc5e7456fb66552 Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 8 Jul 2019 10:52:08 -0400 Subject: [PATCH 25/70] handle dates with store time (magento/magento2#22833: Short-term admin accounts) --- .../Model/UserExpiration/ValidatorTest.php | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php index 18e1eb3438bae..bde468c31ac24 100644 --- a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php @@ -22,16 +22,33 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase */ private $validator; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Framework\Stdlib\DateTime\DateTime + */ + private $dateTimeMock; + protected function setUp() { $objectManager = new ObjectManager($this); - $this->validator = $objectManager->getObject(\Magento\Security\Model\UserExpiration\Validator::class); + $this->dateTimeMock = + $this->createPartialMock(\Magento\Framework\Stdlib\DateTime\DateTime::class, ['gmtTimestamp']); + $this->validator = $objectManager->getObject( + \Magento\Security\Model\UserExpiration\Validator::class, + ['dateTime' => $this->dateTimeMock] + ); } public function testWithPastDate() { + $currentTime = '1562447687'; + $expireTime = '1561324487'; $testDate = new \DateTime(); $testDate->modify('-10 days'); + $this->dateTimeMock->expects(static::exactly(2))->method('gmtTimestamp') + ->willReturnOnConsecutiveCalls( + $currentTime, + $expireTime + ); static::assertFalse($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); static::assertContains( '"Expiration date" must be later than the current date.', @@ -41,8 +58,15 @@ public function testWithPastDate() public function testWithFutureDate() { + $currentTime = '1562447687'; + $expireTime = '1563290841'; $testDate = new \DateTime(); $testDate->modify('+10 days'); + $this->dateTimeMock->expects(static::exactly(2))->method('gmtTimestamp') + ->willReturnOnConsecutiveCalls( + $currentTime, + $expireTime + ); static::assertTrue($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); static::assertEquals([], $this->validator->getMessages()); } From c384fabb5cc324e8ee76ab832335368a96bf8640 Mon Sep 17 00:00:00 2001 From: lfolco Date: Fri, 12 Jul 2019 09:13:12 -0400 Subject: [PATCH 26/70] using new basebox --- pub/phpinfo.php | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pub/phpinfo.php diff --git a/pub/phpinfo.php b/pub/phpinfo.php new file mode 100644 index 0000000000000..e9a1e6e06f0e8 --- /dev/null +++ b/pub/phpinfo.php @@ -0,0 +1,3 @@ + Date: Mon, 29 Jul 2019 10:08:40 -0400 Subject: [PATCH 27/70] fix tests (magento/magento2#22833: Short-term admin accounts) --- .../Model/ResourceModel/UserExpiration.php | 4 ++-- .../Model/UserExpirationManagerTest.php | 20 +++---------------- .../Observer/AfterAdminUserSaveTest.php | 11 ++++++---- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index 21ee2bc077a2b..b3163f6ba12f9 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -69,7 +69,7 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $userExpir { /** @var $userExpiration \Magento\Security\Model\UserExpiration */ $expiresAt = $userExpiration->getExpiresAt(); - $utcValue = $this->dateTime->gmtDate('Y-m-d H:i:s', $expiresAt); + $utcValue = $this->dateTime->gmtDate(null, $expiresAt); $userExpiration->setExpiresAt($utcValue); return $this; @@ -87,7 +87,7 @@ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $userExpira /** @var $userExpiration \Magento\Security\Model\UserExpiration */ if ($userExpiration->getExpiresAt()) { $storeValue = $this->timezone->date($userExpiration->getExpiresAt()); - $userExpiration->setExpiresAt($storeValue); + $userExpiration->setExpiresAt($storeValue->format('Y-m-d H:i:s')); } return $this; diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index a5142d041f0b0..7b9d0ce328203 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -8,7 +8,7 @@ namespace Magento\Security\Model; /** - * TODO: test logging out sessions + * Tests for \Magento\Security\Model\UserExpirationManager */ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase { @@ -50,18 +50,6 @@ protected function setUp() $this->objectManager->create(\Magento\Security\Model\UserExpirationManager::class); } - /** - * Tear down - */ - protected function tearDown() - { - $this->auth = null; - $this->authSession = null; - $this->adminSessionInfo = null; - $this->userExpirationManager = null; - $this->objectManager = null; - } - /** * @magentoDataFixture Magento/Security/_files/expired_users.php */ @@ -74,6 +62,7 @@ public function testUserIsExpired() /** * @magentoDataFixture Magento/Security/_files/expired_users.php + * @magentoAppIsolation enabled */ public function testDeactivateExpiredUsersWithExpiredUser() { @@ -89,16 +78,14 @@ public function testDeactivateExpiredUsersWithExpiredUser() static::assertEquals(0, $user->getIsActive()); static::assertNull($userExpirationModel->getId()); static::assertEquals(AdminSessionInfo::LOGGED_OUT, (int)$this->adminSessionInfo->getStatus()); - $this->auth->logout(); } /** * @magentoDataFixture Magento/Security/_files/expired_users.php + * @magentoAppIsolation enabled */ public function testDeactivateExpiredUsersWithNonExpiredUser() { - // TODO: login fails for the second test that tries to log a user in, doesn't matter which test - // it's trying to create a session for the user ID in the previous test $adminUsernameFromFixture = 'adminUserNotExpired'; $this->loginUser($adminUsernameFromFixture); $user = $this->loadUserByUsername($adminUsernameFromFixture); @@ -110,7 +97,6 @@ public function testDeactivateExpiredUsersWithNonExpiredUser() static::assertEquals(1, $user->getIsActive()); static::assertEquals($user->getId(), $userExpirationModel->getId()); static::assertEquals(AdminSessionInfo::LOGGED_IN, (int)$this->adminSessionInfo->getStatus()); - $this->auth->logout(); } /** diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php index 75b2e1ca2fc32..d33e181192927 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php @@ -23,7 +23,7 @@ class AfterAdminUserSaveTest extends \PHPUnit\Framework\TestCase public function testSaveNewUserExpiration() { $adminUserNameFromFixture = 'dummy_username'; - $testDate = $this->getExpiresDateTime(); + $testDate = $this->getFutureDateInStoreTime(); $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); $user->loadByUsername($adminUserNameFromFixture); $user->setExpiresAt($testDate); @@ -67,7 +67,7 @@ public function testClearUserExpiration() public function testChangeUserExpiration() { $adminUserNameFromFixture = 'adminUserNotExpired'; - $testDate = $this->getExpiresDateTime(); + $testDate = $this->getFutureDateInStoreTime(); $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); $user->loadByUsername($adminUserNameFromFixture); @@ -90,10 +90,13 @@ public function testChangeUserExpiration() * @return string * @throws \Exception */ - private function getExpiresDateTime() + private function getFutureDateInStoreTime() { + /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface $locale */ + $locale = Bootstrap::getObjectManager()->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $testDate = new \DateTime(); $testDate->modify('+20 days'); - return $testDate->format('Y-m-d H:i:s'); + $storeDate = $locale->date($testDate); + return $storeDate->format('Y-m-d H:i:s'); } } From 13038fc17a6c8cada2747f92e62670410a6a03b2 Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 29 Jul 2019 10:42:46 -0400 Subject: [PATCH 28/70] fix unit test (magento/magento2#22833: Short-term admin accounts) --- .../Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php index 263d7b5359b29..b3aafa7cdfbab 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php @@ -95,7 +95,7 @@ public function testWithExpiredUser() ->method('userIsExpired') ->with($this->userMock) ->willReturn(true); - $this->userMock->expects(static::once())->method('getId')->willReturn($adminUserId); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($adminUserId); $this->userExpirationManagerMock->expects(static::once()) ->method('deactivateExpiredUsers') ->with([$adminUserId]) @@ -105,11 +105,12 @@ public function testWithExpiredUser() public function testWithNonExpiredUser() { + $adminUserId = 123; $username = 'testuser'; $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); $this->eventMock->expects(static::once())->method('getUsername')->willReturn($username); $this->userMock->expects(static::once())->method('loadByUsername')->willReturn($this->userMock); - + $this->userMock->expects(static::once())->method('getId')->willReturn($adminUserId); $this->userExpirationManagerMock->expects(static::once()) ->method('userIsExpired') ->with($this->userMock) From 192f8e8518e5027b66b3884a23861eb8d15b7fb9 Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 29 Jul 2019 11:36:58 -0400 Subject: [PATCH 29/70] fix phpdoc (magento/magento2#22833: Short-term admin accounts) --- .../Magento/Security/Model/ResourceModel/UserExpiration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index b3163f6ba12f9..62ddd8dcaaa2f 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -36,7 +36,7 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime - * @param null $connectionName + * @param string $connectionName */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, From c5ac84153e968e97947862ba4bf0b5c32cf0f075 Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 29 Jul 2019 11:38:11 -0400 Subject: [PATCH 30/70] remove phpinfo file (magento/magento2#22833: Short-term admin accounts) --- pub/phpinfo.php | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pub/phpinfo.php diff --git a/pub/phpinfo.php b/pub/phpinfo.php deleted file mode 100644 index e9a1e6e06f0e8..0000000000000 --- a/pub/phpinfo.php +++ /dev/null @@ -1,3 +0,0 @@ - Date: Mon, 29 Jul 2019 14:17:34 -0400 Subject: [PATCH 31/70] test fixes (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/UserExpiration/Validator.php | 5 +++++ .../Magento/Security/Model/Plugin/AuthSessionTest.php | 1 + .../Magento/Security/Model/UserExpirationManagerTest.php | 2 ++ 3 files changed, 8 insertions(+) diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index 20fca2215a899..6a1cd5e2ce038 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -19,6 +19,11 @@ class Validator extends AbstractValidator /**@var \Magento\Framework\Stdlib\DateTime\DateTime */ private $dateTime; + /** + * Validator constructor. + * + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + */ public function __construct( \Magento\Framework\Stdlib\DateTime\DateTime $dateTime ) { diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 1d86c40018659..27b085312186e 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -7,6 +7,7 @@ /** * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AuthSessionTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 7b9d0ce328203..64829566ea3b4 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -9,6 +9,8 @@ /** * Tests for \Magento\Security\Model\UserExpirationManager + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase { From 05fae715b2a50fb601f3453510e605669ce2fc50 Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 29 Jul 2019 15:01:57 -0400 Subject: [PATCH 32/70] use convertConfigTimeToUtc to get UTC time (magento/magento2#22833: Short-term admin accounts) --- .../Magento/Security/Model/ResourceModel/UserExpiration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index 62ddd8dcaaa2f..9ff5d0d9f7400 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -64,12 +64,13 @@ protected function _construct() * * @param \Magento\Framework\Model\AbstractModel $userExpiration * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $userExpiration) { /** @var $userExpiration \Magento\Security\Model\UserExpiration */ $expiresAt = $userExpiration->getExpiresAt(); - $utcValue = $this->dateTime->gmtDate(null, $expiresAt); + $utcValue = $this->timezone->convertConfigTimeToUtc($expiresAt); $userExpiration->setExpiresAt($utcValue); return $this; From 6378fe6e20df122be1026c7c35dc0fdc3a3a46eb Mon Sep 17 00:00:00 2001 From: lfolco Date: Mon, 29 Jul 2019 15:58:00 -0400 Subject: [PATCH 33/70] remove unused constructor param (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/ResourceModel/UserExpiration.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index 9ff5d0d9f7400..ec4a2bf7e42a0 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -25,28 +25,20 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD */ private $timezone; - /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime - */ - private $dateTime; - /** * UserExpiration constructor. * * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param string $connectionName */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, $connectionName = null ) { parent::__construct($context, $connectionName); $this->timezone = $timezone; - $this->dateTime = $dateTime; } /** From 437cbf0a81b7f8045579a357529c4f9eecf7ec32 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sat, 21 Sep 2019 12:16:00 -0400 Subject: [PATCH 34/70] suppress error about "sessions or cookies while not being a part of HTML Presentation layer" (#22833) --- app/code/Magento/Security/Model/UserExpirationManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index 44e8226e391ed..aa3ac177bb914 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -11,6 +11,7 @@ /** * Class to handle admin user expirations. + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class UserExpirationManager { From 60f5710e0c2789187082ed96d8f24a416a426fa4 Mon Sep 17 00:00:00 2001 From: lfolco Date: Sun, 22 Sep 2019 17:08:45 -0400 Subject: [PATCH 35/70] add MFTF test for creating new user with valid expiration date (#22833) --- ...teNewUserWithUserExpirationActionGroup.xml | 34 +++++++++++++++++++ .../Security/Test/Mftf/Data/UserData.xml | 29 ++++++++++++++++ .../Mftf/Section/AdminEditUserSection.xml | 13 +++++++ .../Mftf/Section/AdminNewUserFormSection.xml | 13 +++++++ ...erCreateNewUserWithValidExpirationTest.xml | 30 ++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Data/UserData.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Section/AdminEditUserSection.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Section/AdminNewUserFormSection.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml new file mode 100644 index 0000000000000..a0250ca3ef4fc --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml @@ -0,0 +1,34 @@ + + + + + + + Goes to the Admin Users grid page. Clicks on Create User. Fills in the provided User with an expiration date and saves. + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Security/Test/Mftf/Data/UserData.xml b/app/code/Magento/Security/Test/Mftf/Data/UserData.xml new file mode 100644 index 0000000000000..5256d2ca4a28e --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Data/UserData.xml @@ -0,0 +1,29 @@ + + + + + + adminExpired + John + Doe + admin@example.com + 123123q + 123123q + en_US + English (United States) + true + Active + {{_ENV.MAGENTO_ADMIN_PASSWORD}} + Administrators + Sep 3, 2030 4:42:36 PM + + 1 + + + diff --git a/app/code/Magento/Security/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/Security/Test/Mftf/Section/AdminEditUserSection.xml new file mode 100644 index 0000000000000..d7acf2466c09e --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Section/AdminEditUserSection.xml @@ -0,0 +1,13 @@ + + + +
+ +
+
diff --git a/app/code/Magento/Security/Test/Mftf/Section/AdminNewUserFormSection.xml b/app/code/Magento/Security/Test/Mftf/Section/AdminNewUserFormSection.xml new file mode 100644 index 0000000000000..1d1aba4da07dd --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Section/AdminNewUserFormSection.xml @@ -0,0 +1,13 @@ + + + +
+ +
+
diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml new file mode 100644 index 0000000000000..f84d9baa8ebfe --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + <description value="Create new user with expiration date."/> + <testCaseId value="" /> + <severity value="CRITICAL"/> + <group value="security"/> + </annotations> + <before> + <!-- Log in to Admin Panel --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!-- Create new user with user expiration set --> + <actionGroup ref="AdminCreateNewUserWithUserExpirationActionGroup" stepKey="createNewUserWithValidExpiration"> + <argument name="user" value="NewAdminUserWithValidExpiration" /> + </actionGroup> + </test> +</tests> From 9e82e91bbcf4db0fe4815d774bdb03cc4fcd75cc Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 22 Sep 2019 17:51:40 -0400 Subject: [PATCH 36/70] add MFTF test for creating new user with invalid expiration date (#22833) --- ...reateNewUserWithExpirationActionGroup.xml} | 5 +-- ...inSaveUserInvalidExpirationActionGroup.xml | 16 ++++++++++ .../AdminSaveUserSuccessActionGroup.xml | 16 ++++++++++ .../Security/Test/Mftf/Data/UserData.xml | 7 +++- ...CreateNewUserWithInvalidExpirationTest.xml | 32 +++++++++++++++++++ ...erCreateNewUserWithValidExpirationTest.xml | 14 ++++---- 6 files changed, 79 insertions(+), 11 deletions(-) rename app/code/Magento/Security/Test/Mftf/ActionGroup/{AdminCreateNewUserWithUserExpirationActionGroup.xml => AdminCreateNewUserWithExpirationActionGroup.xml} (93%) create mode 100644 app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml create mode 100644 app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml similarity index 93% rename from app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml rename to app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml index a0250ca3ef4fc..5ebd9d723ed26 100644 --- a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithUserExpirationActionGroup.xml +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml @@ -5,10 +5,9 @@ * See COPYING.txt for license details. */ --> - <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCreateNewUserWithUserExpirationActionGroup"> + <actionGroup name="AdminCreateNewUserWithExpirationActionGroup"> <annotations> <description>Goes to the Admin Users grid page. Clicks on Create User. Fills in the provided User with an expiration date and saves.</description> </annotations> @@ -28,7 +27,5 @@ <fillField selector="{{AdminNewUserFormSection.currentPassword}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterCurrentPassword" /> <click selector="{{AdminNewUserFormSection.save}}" stepKey="clickSaveUser" /> <waitForPageLoad stepKey="waitForSaveTheUser" /> - <see userInput="You saved the user." stepKey="seeSuccessMessage" /> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml new file mode 100644 index 0000000000000..8fc890acebf84 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveUserInvalidExpirationActionGroup"> + <annotations> + <description>Error message for saving an admin user with an invalid expiration date.</description> + </annotations> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='"Expiration date" must be later than the current date.' stepKey="seeErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml new file mode 100644 index 0000000000000..5c9b520a19a40 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveUserSuccessActionGroup"> + <annotations> + <description>Success message for saving an admin user successfully.</description> + </annotations> + <see userInput="You saved the user." stepKey="seeSuccessMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/Data/UserData.xml b/app/code/Magento/Security/Test/Mftf/Data/UserData.xml index 5256d2ca4a28e..43c75da1c37ee 100644 --- a/app/code/Magento/Security/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/Security/Test/Mftf/Data/UserData.xml @@ -8,8 +8,9 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewAdminUserWithValidExpiration" type="user"> - <data key="username" unique="suffix">adminExpired</data> + <data key="username" unique="suffix">adminExpiresAt</data> <data key="firstname">John</data> <data key="lastname">Doe</data> <data key="email" unique="prefix">admin@example.com</data> @@ -26,4 +27,8 @@ <item>1</item> </array> </entity> + + <entity name="NewAdminUserWithInvalidExpiration" type="user" extends="NewAdminUserWithValidExpiration"> + <data key="expires_at">Sep 3, 2000 4:42:36 PM</data> + </entity> </entities> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml new file mode 100644 index 0000000000000..18c999a89b2b1 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUserCreateNewUserWithInvalidExpirationTest"> + <annotations> + <features value="Security"/> + <stories value="Create new user with invalid expiration date."/> + <title value="Create new user with invalid expiration date"/> + <description value="Create new user with invalid expiration date."/> + <testCaseId value="" /> + <severity value="CRITICAL"/> + <group value="security_userexpiration"/> + </annotations> + <before> + <!-- Log in to Admin Panel --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!-- Create new user with user expiration set --> + <actionGroup ref="AdminCreateNewUserWithExpirationActionGroup" stepKey="fillInNewUserWithInvalidExpiration"> + <argument name="user" value="NewAdminUserWithInvalidExpiration" /> + </actionGroup> + + <actionGroup ref="AdminSaveUserInvalidExpirationActionGroup" stepKey="saveNewUserWithInvalidExpirationError" /> + </test> +</tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml index f84d9baa8ebfe..cfc94920a13b6 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml @@ -7,15 +7,15 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminUserCreateNewUserWithExpirationTest"> + <test name="AdminUserCreateNewUserWithValidExpirationTest"> <annotations> <features value="Security"/> - <stories value="Create new user with expiration date."/> - <title value="Create new user with expiration date"/> - <description value="Create new user with expiration date."/> + <stories value="Create new user with valid expiration date."/> + <title value="Create new user with valid expiration date"/> + <description value="Create new user with valid expiration date."/> <testCaseId value="" /> <severity value="CRITICAL"/> - <group value="security"/> + <group value="security_userexpiration"/> </annotations> <before> <!-- Log in to Admin Panel --> @@ -23,8 +23,10 @@ </before> <!-- Create new user with user expiration set --> - <actionGroup ref="AdminCreateNewUserWithUserExpirationActionGroup" stepKey="createNewUserWithValidExpiration"> + <actionGroup ref="AdminCreateNewUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="user" value="NewAdminUserWithValidExpiration" /> </actionGroup> + + <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess" /> </test> </tests> From bad8f2b731a400a22bf47a8f748b4818ed2a7bdf Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Thu, 26 Sep 2019 14:06:56 -0400 Subject: [PATCH 37/70] rename tests (magento/magento2#22833: Short-term admin accounts) --- ...onTest.xml => AdminCreateNewUserWithInvalidExpirationTest.xml} | 0 ...tionTest.xml => AdminCreateNewUserWithValidExpirationTest.xml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/code/Magento/Security/Test/Mftf/Test/{AdminUserCreateNewUserWithInvalidExpirationTest.xml => AdminCreateNewUserWithInvalidExpirationTest.xml} (100%) rename app/code/Magento/Security/Test/Mftf/Test/{AdminUserCreateNewUserWithValidExpirationTest.xml => AdminCreateNewUserWithValidExpirationTest.xml} (100%) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml similarity index 100% rename from app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithInvalidExpirationTest.xml rename to app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml similarity index 100% rename from app/code/Magento/Security/Test/Mftf/Test/AdminUserCreateNewUserWithValidExpirationTest.xml rename to app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml From 581988a72634565f84e3bfe94377956777581080 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Fri, 27 Sep 2019 19:16:51 -0400 Subject: [PATCH 38/70] refactored tests to use more from User module (magento/magento2#22833: Short-term admin accounts) --- ...CreateNewUserWithExpirationActionGroup.xml | 31 ----------------- ...minFillInUserWithExpirationActionGroup.xml | 21 ++++++++++++ ...inSaveUserInvalidExpirationActionGroup.xml | 8 ++++- .../AdminSaveUserSuccessActionGroup.xml | 2 ++ .../Security/Test/Mftf/Data/UserData.xml | 34 ------------------- ...CreateNewUserWithInvalidExpirationTest.xml | 15 ++++---- ...inCreateNewUserWithValidExpirationTest.xml | 14 +++++--- ...llNewUserFormRequiredFieldsActionGroup.xml | 2 +- 8 files changed, 49 insertions(+), 78 deletions(-) delete mode 100644 app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml create mode 100644 app/code/Magento/Security/Test/Mftf/ActionGroup/AdminFillInUserWithExpirationActionGroup.xml delete mode 100644 app/code/Magento/Security/Test/Mftf/Data/UserData.xml diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml deleted file mode 100644 index 5ebd9d723ed26..0000000000000 --- a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminCreateNewUserWithExpirationActionGroup.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCreateNewUserWithExpirationActionGroup"> - <annotations> - <description>Goes to the Admin Users grid page. Clicks on Create User. Fills in the provided User with an expiration date and saves.</description> - </annotations> - <arguments> - <argument name="user" defaultValue="NewAdminUserWithValidExpiration"/> - </arguments> - - <amOnPage url="{{AdminNewUserPage.url}}" stepKey="navigateToNewUser"/> - <waitForPageLoad stepKey="waitForUsersPage" /> - <fillField selector="{{AdminNewUserFormSection.username}}" userInput="{{user.username}}" stepKey="enterUserName" /> - <fillField selector="{{AdminNewUserFormSection.firstname}}" userInput="{{user.firstName}}" stepKey="enterFirstName" /> - <fillField selector="{{AdminNewUserFormSection.lastname}}" userInput="{{user.lastName}}" stepKey="enterLastName" /> - <fillField selector="{{AdminNewUserFormSection.email}}" userInput="{{user.username}}@magento.com" stepKey="enterEmail" /> - <fillField selector="{{AdminNewUserFormSection.password}}" userInput="{{user.password}}" stepKey="enterPassword" /> - <fillField selector="{{AdminNewUserFormSection.passwordConfirmation}}" userInput="{{user.password}}" stepKey="confirmPassword" /> - <fillField selector="{{AdminNewUserFormSection.userExpiresField}}" userInput="{{user.expires_at}}" stepKey="enterUserExpiration" /> - <fillField selector="{{AdminNewUserFormSection.currentPassword}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterCurrentPassword" /> - <click selector="{{AdminNewUserFormSection.save}}" stepKey="clickSaveUser" /> - <waitForPageLoad stepKey="waitForSaveTheUser" /> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminFillInUserWithExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminFillInUserWithExpirationActionGroup.xml new file mode 100644 index 0000000000000..d1d5427506e40 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminFillInUserWithExpirationActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillInUserWithExpirationActionGroup" + extends="AdminFillNewUserFormRequiredFieldsActionGroup"> + <annotations> + <description>Goes to the Admin Users grid page. Clicks on Create User. Fills in the provided User details with a expiration date.</description> + </annotations> + <arguments> + <argument name="expires_at" type="string"/> + </arguments> + <fillField selector="{{AdminNewUserFormSection.userExpiresField}}" userInput="{{expires_at}}" + stepKey="fillExpireDate" after="fillPasswordConfirmation"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml index 8fc890acebf84..02280ed809124 100644 --- a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserInvalidExpirationActionGroup.xml @@ -11,6 +11,12 @@ <annotations> <description>Error message for saving an admin user with an invalid expiration date.</description> </annotations> - <see selector="{{AdminMessagesSection.errorMessage}}" userInput='"Expiration date" must be later than the current date.' stepKey="seeErrorMessage"/> + <click selector="{{AdminNewUserFormSection.save}}" stepKey="clickSaveUser" /> + <waitForPageLoad stepKey="waitForSaveTheUser" /> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewUserFormSection.userInfoTab}}" stepKey="openUserTab"/> + <waitForPageLoad stepKey="waitForUserRoleTabOpened"/> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='"Expiration date" must be later than the current date.' + stepKey="seeErrorMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml index 5c9b520a19a40..7aed1e07112cf 100644 --- a/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml +++ b/app/code/Magento/Security/Test/Mftf/ActionGroup/AdminSaveUserSuccessActionGroup.xml @@ -11,6 +11,8 @@ <annotations> <description>Success message for saving an admin user successfully.</description> </annotations> + <click selector="{{AdminNewUserFormSection.save}}" stepKey="clickSaveUser" /> + <waitForPageLoad stepKey="waitForSaveTheUser" /> <see userInput="You saved the user." stepKey="seeSuccessMessage" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Security/Test/Mftf/Data/UserData.xml b/app/code/Magento/Security/Test/Mftf/Data/UserData.xml deleted file mode 100644 index 43c75da1c37ee..0000000000000 --- a/app/code/Magento/Security/Test/Mftf/Data/UserData.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> - - <entity name="NewAdminUserWithValidExpiration" type="user"> - <data key="username" unique="suffix">adminExpiresAt</data> - <data key="firstname">John</data> - <data key="lastname">Doe</data> - <data key="email" unique="prefix">admin@example.com</data> - <data key="password">123123q</data> - <data key="password_confirmation">123123q</data> - <data key="interface_local">en_US</data> - <data key="interface_local_label">English (United States)</data> - <data key="is_active">true</data> - <data key="is_active_label">Active</data> - <data key="current_password">{{_ENV.MAGENTO_ADMIN_PASSWORD}}</data> - <data key="role">Administrators</data> - <data key="expires_at">Sep 3, 2030 4:42:36 PM</data> - <array key="roles"> - <item>1</item> - </array> - </entity> - - <entity name="NewAdminUserWithInvalidExpiration" type="user" extends="NewAdminUserWithValidExpiration"> - <data key="expires_at">Sep 3, 2000 4:42:36 PM</data> - </entity> -</entities> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml index 18c999a89b2b1..4fb14742f48d7 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml @@ -15,18 +15,21 @@ <description value="Create new user with invalid expiration date."/> <testCaseId value="" /> <severity value="CRITICAL"/> - <group value="security_userexpiration"/> + <group value="security_userexpire"/> </annotations> + <before> - <!-- Log in to Admin Panel --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> - <!-- Create new user with user expiration set --> - <actionGroup ref="AdminCreateNewUserWithExpirationActionGroup" stepKey="fillInNewUserWithInvalidExpiration"> - <argument name="user" value="NewAdminUserWithInvalidExpiration" /> + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + <generateDate date="-5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> + <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> - <actionGroup ref="AdminSaveUserInvalidExpirationActionGroup" stepKey="saveNewUserWithInvalidExpirationError" /> </test> </tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml index cfc94920a13b6..acfa0464a6045 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml @@ -15,18 +15,22 @@ <description value="Create new user with valid expiration date."/> <testCaseId value="" /> <severity value="CRITICAL"/> - <group value="security_userexpiration"/> + <group value="security_userexpire"/> </annotations> <before> - <!-- Log in to Admin Panel --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> - <!-- Create new user with user expiration set --> - <actionGroup ref="AdminCreateNewUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> - <argument name="user" value="NewAdminUserWithValidExpiration" /> + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + <generateDate date="+5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> + <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> + <!-- TODO: get proper date format to match above --> <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess" /> </test> </tests> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml index eb3ef37056b2f..15cb4e5319904 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml @@ -13,7 +13,7 @@ <description>Fills in the provided User details on the New User creation page.</description> </annotations> <arguments> - <argument name="user" type="entity"/> + <argument name="user" defaultValue="NewAdminUser"/> </arguments> <fillField selector="{{AdminNewUserFormSection.username}}" userInput="{{user.username}}" stepKey="fillUser"/> From f1858064cf19ecd7fd3662c3907d293e05d699d4 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 28 Sep 2019 15:06:33 -0400 Subject: [PATCH 39/70] added test to ensure timezones working correctly (magento/magento2#22833: Short-term admin accounts) --- .../Observer/AfterAdminUserSaveTest.php | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php index d33e181192927..ee2ae79b62cf8 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserSaveTest.php @@ -38,6 +38,29 @@ public function testSaveNewUserExpiration() static::assertEquals($userExpiration->getExpiresAt(), $testDate); } + /** + * Save a new UserExpiration; used to validate that date conversion is working correctly. + * + * @magentoDataFixture Magento/User/_files/dummy_user.php + */ + public function testSaveNewUserExpirationInMinutes() + { + $adminUserNameFromFixture = 'dummy_username'; + $testDate = $this->getFutureDateInStoreTime('+2 minutes'); + $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); + $user->loadByUsername($adminUserNameFromFixture); + $user->setExpiresAt($testDate); + $user->save(); + + $userExpirationFactory = + Bootstrap::getObjectManager()->create(\Magento\Security\Model\UserExpirationFactory::class); + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $userExpirationFactory->create(); + $userExpiration->load($user->getId()); + static::assertNotNull($userExpiration->getId()); + static::assertEquals($userExpiration->getExpiresAt(), $testDate); + } + /** * Remove the UserExpiration record * @@ -87,15 +110,16 @@ public function testChangeUserExpiration() } /** + * @param string $timeToAdd Amount of time to add * @return string * @throws \Exception */ - private function getFutureDateInStoreTime() + private function getFutureDateInStoreTime($timeToAdd = '+20 days') { /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface $locale */ $locale = Bootstrap::getObjectManager()->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $testDate = new \DateTime(); - $testDate->modify('+20 days'); + $testDate->modify($timeToAdd); $storeDate = $locale->date($testDate); return $storeDate->format('Y-m-d H:i:s'); } From d8c8473ff10b29d37d40480180c04633475e800f Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 28 Sep 2019 15:25:30 -0400 Subject: [PATCH 40/70] use UTC to convert date in validator (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/UserExpiration/Validator.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index 6a1cd5e2ce038..a2211283e4cb3 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -16,19 +16,19 @@ */ class Validator extends AbstractValidator { - /**@var \Magento\Framework\Stdlib\DateTime\DateTime */ - private $dateTime; + + /**@var \Magento\Framework\Stdlib\DateTime\TimezoneInterface */ + private $timezone; /** * Validator constructor. * - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone */ public function __construct( - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone ) { - - $this->dateTime = $dateTime; + $this->timezone = $timezone; } /** @@ -45,8 +45,9 @@ public function isValid($value) $expiresAt = $value; $label = 'Expiration date'; if (\Zend_Validate::is($expiresAt, 'NotEmpty')) { - $currentTime = $this->dateTime->gmtTimestamp(); - $expiresAt = $this->dateTime->gmtTimestamp($value); + $currentTime = $this->timezone->date()->getTimestamp(); + $utcExpiresAt = $this->timezone->convertConfigTimeToUtc($expiresAt); + $expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp(); if ($expiresAt < $currentTime) { $messages['expires_at'] = __('"%1" must be later than the current date.', $label); } From 0f91a9df6e3bb9e976df37ad4d2cf3d8fda85b9e Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 29 Sep 2019 12:23:21 -0400 Subject: [PATCH 41/70] add MFTF tests for logging in and fix timezone issue (magento/magento2#22833: Short-term admin accounts) --- .../Model/UserExpiration/Validator.php | 10 ++++- ...CreateNewUserWithInvalidExpirationTest.xml | 2 +- ...inCreateNewUserWithValidExpirationTest.xml | 4 +- ...minLoginAdminUserWithInvalidExpiration.xml | 37 ++++++++++++++++++ ...AdminLoginAdminUserWithValidExpiration.xml | 39 +++++++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml create mode 100644 app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index a2211283e4cb3..b132c122cbc34 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -20,15 +20,21 @@ class Validator extends AbstractValidator /**@var \Magento\Framework\Stdlib\DateTime\TimezoneInterface */ private $timezone; + /**@var \Magento\Framework\Stdlib\DateTime\DateTime */ + private $dateTime; + /** * Validator constructor. * * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime */ public function __construct( - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime ) { $this->timezone = $timezone; + $this->dateTime = $dateTime; } /** @@ -45,7 +51,7 @@ public function isValid($value) $expiresAt = $value; $label = 'Expiration date'; if (\Zend_Validate::is($expiresAt, 'NotEmpty')) { - $currentTime = $this->timezone->date()->getTimestamp(); + $currentTime = $this->dateTime->gmtTimestamp(); $utcExpiresAt = $this->timezone->convertConfigTimeToUtc($expiresAt); $expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp(); if ($expiresAt < $currentTime) { diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml index 4fb14742f48d7..6bcd7be784f88 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml @@ -15,7 +15,7 @@ <description value="Create new user with invalid expiration date."/> <testCaseId value="" /> <severity value="CRITICAL"/> - <group value="security_userexpire"/> + <group value="security"/> </annotations> <before> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml index acfa0464a6045..f798865ab2279 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml @@ -15,7 +15,7 @@ <description value="Create new user with valid expiration date."/> <testCaseId value="" /> <severity value="CRITICAL"/> - <group value="security_userexpire"/> + <group value="security"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -29,8 +29,6 @@ <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> - - <!-- TODO: get proper date format to match above --> <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess" /> </test> </tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml new file mode 100644 index 0000000000000..f207d57c41084 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + + <test name="AdminLoginAdminUserWithInvalidExpiration"> + <annotations> + <features value="Security"/> + <stories value="Try to login as a user with an invalid expiration date."/> + <title value="Try to login as a user with an invalid expiration date"/> + <description value="Try to login as a user with an invalid expiration date."/> + <testCaseId value=""/> + <severity value="CRITICAL"/> + <group value="security"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + <generateDate date="+1 minute" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> + <argument name="expires_at" value="{$expiresDateTime}"/> + </actionGroup> + <grabValueFrom selector="{{AdminNewUserFormSection.username}}" stepKey="grabUsername"/> + <grabValueFrom selector="{{AdminNewUserFormSection.password}}" stepKey="grabPassword"/> + <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess"/> + <actionGroup ref="logout" stepKey="logout"/> + <wait time="60" stepKey="waitForUserToExpire"/> + <actionGroup ref="LoginAdminWithCredentialsActionGroup" stepKey="loginAsNewAdmin"> + <argument name="adminUser" value="{$grabUsername}"/> + <argument name="adminPassword" value="{$grabPassword}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="checkLoginMessage" /> + + </test> +</tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml new file mode 100644 index 0000000000000..92c538982859e --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + + <test name="AdminLoginAdminUserWithValidExpiration"> + <annotations> + <features value="Security"/> + <stories value="Login as a user with a valid expiration date."/> + <title value="Login as a user with a valid expiration date"/> + <description value="Login as a user with a valid expiration date."/> + <testCaseId value=""/> + <severity value="CRITICAL"/> + <group value="security"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + <generateDate date="+5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> + <argument name="expires_at" value="{$expiresDateTime}"/> + </actionGroup> + <grabValueFrom selector="{{AdminNewUserFormSection.username}}" stepKey="grabUsername"/> + <grabValueFrom selector="{{AdminNewUserFormSection.password}}" stepKey="grabPassword"/> + <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess"/> + <actionGroup ref="logout" stepKey="logout"/> + <actionGroup ref="LoginAdminWithCredentialsActionGroup" stepKey="loginAsNewAdmin"> + <argument name="adminUser" value="{$grabUsername}"/> + <argument name="adminPassword" value="{$grabPassword}"/> + </actionGroup> + <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> + + </test> +</tests> From 45d648f322cbdc9b7bd16b12ffd97c5677a400b3 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 29 Sep 2019 13:52:43 -0400 Subject: [PATCH 42/70] Update unit tests to handle new timezone dependency (magento/magento2#22833: Short-term admin accounts) --- .../Model/UserExpiration/ValidatorTest.php | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php index bde468c31ac24..b000ebbac96cb 100644 --- a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php @@ -27,29 +27,42 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase */ private $dateTimeMock; + /**@var \PHPUnit\Framework\MockObject\MockObject|\Magento\Framework\Stdlib\DateTime\TimezoneInterface */ + private $timezoneMock; + protected function setUp() { $objectManager = new ObjectManager($this); $this->dateTimeMock = $this->createPartialMock(\Magento\Framework\Stdlib\DateTime\DateTime::class, ['gmtTimestamp']); + $this->timezoneMock = + $this->createPartialMock( + \Magento\Framework\Stdlib\DateTime\Timezone::class, + ['date', 'convertConfigTimeToUtc'] + ); $this->validator = $objectManager->getObject( \Magento\Security\Model\UserExpiration\Validator::class, - ['dateTime' => $this->dateTimeMock] + ['dateTime' => $this->dateTimeMock, 'timezone' => $this->timezoneMock] ); } public function testWithPastDate() { - $currentTime = '1562447687'; - $expireTime = '1561324487'; - $testDate = new \DateTime(); - $testDate->modify('-10 days'); - $this->dateTimeMock->expects(static::exactly(2))->method('gmtTimestamp') - ->willReturnOnConsecutiveCalls( - $currentTime, - $expireTime - ); - static::assertFalse($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); + /** @var \DateTime|\PHPUnit_Framework_MockObject_MockObject $dateObject */ + $dateObject = $this->createMock(\DateTime::class); + $this->timezoneMock->expects(static::once()) + ->method('date') + ->will(static::returnValue($dateObject)); + + $currentDate = new \DateTime(); + $currentDate = $currentDate->getTimestamp(); + $expireDate = new \DateTime(); + $expireDate->modify('-10 days'); + + $this->dateTimeMock->expects(static::once())->method('gmtTimestamp')->willReturn($currentDate); + $this->timezoneMock->expects(static::once())->method('date')->willReturn($expireDate); + $dateObject->expects(static::once())->method('getTimestamp')->willReturn($expireDate->getTimestamp()); + static::assertFalse($this->validator->isValid($expireDate->format('Y-m-d H:i:s'))); static::assertContains( '"Expiration date" must be later than the current date.', $this->validator->getMessages() @@ -58,16 +71,20 @@ public function testWithPastDate() public function testWithFutureDate() { - $currentTime = '1562447687'; - $expireTime = '1563290841'; - $testDate = new \DateTime(); - $testDate->modify('+10 days'); - $this->dateTimeMock->expects(static::exactly(2))->method('gmtTimestamp') - ->willReturnOnConsecutiveCalls( - $currentTime, - $expireTime - ); - static::assertTrue($this->validator->isValid($testDate->format('Y-m-d H:i:s'))); + /** @var \DateTime|\PHPUnit_Framework_MockObject_MockObject $dateObject */ + $dateObject = $this->createMock(\DateTime::class); + $this->timezoneMock->expects(static::once()) + ->method('date') + ->will(static::returnValue($dateObject)); + $currentDate = new \DateTime(); + $currentDate = $currentDate->getTimestamp(); + $expireDate = new \DateTime(); + $expireDate->modify('+10 days'); + + $this->dateTimeMock->expects(static::once())->method('gmtTimestamp')->willReturn($currentDate); + $this->timezoneMock->expects(static::once())->method('date')->willReturn($expireDate); + $dateObject->expects(static::once())->method('getTimestamp')->willReturn($expireDate->getTimestamp()); + static::assertTrue($this->validator->isValid($expireDate->format('Y-m-d H:i:s'))); static::assertEquals([], $this->validator->getMessages()); } } From 970510c6e1494cddfcdb756bc0262eaafe0b22f2 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 29 Sep 2019 16:51:01 -0400 Subject: [PATCH 43/70] add copyright and fix failing unrelated test (magento/magento2#22833: Short-term admin accounts) --- .../Test/Mftf/Test/AdminConfigurationPermissionTest.xml | 3 ++- .../Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml index 58e809ec45c4a..a079193e97a65 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml @@ -35,8 +35,9 @@ <waitForPageLoad time="30" stepKey="wait2"/> <seeInField selector="{{AdminEditUserSection.usernameTextField}}" userInput="$$noReportUser.username$$" stepKey="seeUsernameInField"/> <fillField selector="{{AdminEditUserSection.currentPasswordField}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillCurrentPassword"/> - <click selector="{{AdminEditUserSection.userRoleTab}}" stepKey="clickUserRoleTab"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminEditUserSection.userRoleTab}}" stepKey="clickUserRoleTab"/> <fillField selector="{{AdminEditUserSection.roleNameFilterTextField}}" userInput="$$noReportUserRole.rolename$$" stepKey="fillRoleNameSearch"/> <click selector="{{AdminEditUserSection.searchButton}}" stepKey="clickSearchButtonUserRole"/> <waitForPageLoad time="10" stepKey="wait3"/> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index f207d57c41084..e1e0a53606a57 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -1,4 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> From 78dc3b5ebb2e7d77a444401c349eff195ad535ee Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 29 Sep 2019 17:22:04 -0400 Subject: [PATCH 44/70] add copyright (magento/magento2#22833: Short-term admin accounts) --- .../Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml index 92c538982859e..f368a71d84d85 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml @@ -1,4 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> From 171c4c9420fd071d6ca90924cca22a075e12f12f Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 30 Sep 2019 09:00:10 -0400 Subject: [PATCH 45/70] increase lead time for user expiration test (magento/magento2#22833: Short-term admin accounts) --- .../Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index e1e0a53606a57..ee8dfdb5cb085 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -25,7 +25,7 @@ </before> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+1 minute" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <generateDate date="+2 minute" format="M d, Y h:i:s" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> @@ -33,7 +33,7 @@ <grabValueFrom selector="{{AdminNewUserFormSection.password}}" stepKey="grabPassword"/> <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess"/> <actionGroup ref="logout" stepKey="logout"/> - <wait time="60" stepKey="waitForUserToExpire"/> + <wait time="120" stepKey="waitForUserToExpire"/> <actionGroup ref="LoginAdminWithCredentialsActionGroup" stepKey="loginAsNewAdmin"> <argument name="adminUser" value="{$grabUsername}"/> <argument name="adminPassword" value="{$grabPassword}"/> From 89b8512cc209a67188feb60870bbd9c13a2f39fe Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 30 Sep 2019 15:10:39 -0400 Subject: [PATCH 46/70] fixes from code review (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/Plugin/AuthSession.php | 2 +- .../Model/ResourceModel/UserExpiration.php | 2 +- .../UserExpiration/Collection.php | 13 +++---- .../Magento/Security/Model/UserExpiration.php | 2 ++ .../Security/Model/UserExpirationManager.php | 12 ++++--- .../Observer/AdminUserAuthenticateBefore.php | 15 ++++---- .../Unit/Model/Plugin/AuthSessionTest.php | 20 ++++++++--- ...hp => AdminUserAuthenticateBeforeTest.php} | 34 ++++++++++++------- .../Magento/User/etc/db_schema_whitelist.json | 2 +- .../Model/UserExpirationManagerTest.php | 6 ++-- 10 files changed, 63 insertions(+), 45 deletions(-) rename app/code/Magento/Security/Test/Unit/Observer/{BeforeAdminUserAuthenticateTest.php => AdminUserAuthenticateBeforeTest.php} (79%) diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index 007f0871f70d1..910d0058e7021 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -72,7 +72,7 @@ public function __construct( public function aroundProlong(Session $session, \Closure $proceed) { if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus() || - $this->userExpirationManager->userIsExpired($session->getUser())) { + $this->userExpirationManager->isUserExpired($session->getUser()->getId())) { $session->destroy(); $this->addUserLogoutNotification(); return null; diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php index ec4a2bf7e42a0..71a331a178006 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration.php @@ -35,7 +35,7 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, - $connectionName = null + ?string $connectionName = null ) { parent::__construct($context, $connectionName); $this->timezone = $timezone; diff --git a/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php index dee92c599f4de..2f2971bc90225 100644 --- a/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php +++ b/app/code/Magento/Security/Model/ResourceModel/UserExpiration/Collection.php @@ -33,21 +33,18 @@ protected function _construct() /** * Filter for expired, active users. * - * @param string $now * @return $this */ - public function addActiveExpiredUsersFilter($now = null): Collection + public function addActiveExpiredUsersFilter(): Collection { - if ($now === null) { - $now = new \DateTime(); - $now->format('Y-m-d H:i:s'); - } + $currentTime = new \DateTime(); + $currentTime->format('Y-m-d H:i:s'); $this->getSelect()->joinLeft( ['user' => $this->getTable('admin_user')], 'main_table.user_id = user.user_id', ['is_active'] ); - $this->addFieldToFilter('expires_at', ['lt' => $now]) + $this->addFieldToFilter('expires_at', ['lt' => $currentTime]) ->addFieldToFilter('user.is_active', 1); return $this; @@ -59,7 +56,7 @@ public function addActiveExpiredUsersFilter($now = null): Collection * @param int[] $userIds * @return Collection */ - public function addUserIdsFilter($userIds = []): Collection + public function addUserIdsFilter(array $userIds = []): Collection { return $this->addFieldToFilter('main_table.user_id', ['in' => $userIds]); } diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index 0e70243f30117..08f60ade672b6 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -9,6 +9,8 @@ /** * Admin User Expiration model. + * @method string getUserId() + * @method \Magento\Security\Model\UserExpiration setUserId($userId) * @method string getExpiresAt() * @method \Magento\Security\Model\UserExpiration setExpiresAt($value) */ diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index aa3ac177bb914..9b26935c1baf2 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -10,7 +10,9 @@ use Magento\Security\Model\ResourceModel\UserExpiration\Collection as ExpiredUsersCollection; /** - * Class to handle admin user expirations. + * Class to handle admin user expirations. Temporary admin users can be created with an expiration + * date that, once past, will not allow them to login to the admin. A cron is run to periodically check for expired + * users and, if found, will deactivate them. * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class UserExpirationManager @@ -98,26 +100,26 @@ public function deactivateExpiredUsers(?array $userIds = null): void // delete expired records $expiredRecordIds = $expiredRecords->getAllIds(); - $expiredRecords->walk('delete'); // set user is_active to 0 $users = $this->userCollectionFactory->create() ->addFieldToFilter('main_table.user_id', ['in' => $expiredRecordIds]); $users->setDataToAll('is_active', 0)->save(); + $expiredRecords->walk('delete'); } /** * Check if the given user is expired. * - * @param \Magento\User\Model\User $user + * @param string $userId * @return bool */ - public function userIsExpired(\Magento\User\Model\User $user): bool + public function isUserExpired(string $userId): bool { $isExpired = false; /** @var \Magento\Security\Model\UserExpiration $expiredRecord */ $expiredRecord = $this->userExpirationCollectionFactory->create() - ->addExpiredRecordsForUserFilter($user->getId()) + ->addExpiredRecordsForUserFilter($userId) ->getFirstItem(); if ($expiredRecord && $expiredRecord->getId()) { $expiresAt = $this->dateTime->timestamp($expiredRecord->getExpiresAt()); diff --git a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index d40f25a023bbc..1ffdd1cd1d11e 100644 --- a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -25,22 +25,22 @@ class AdminUserAuthenticateBefore implements ObserverInterface private $userExpirationManager; /** - * @var \Magento\User\Model\User + * @var \Magento\User\Model\UserFactory */ - private $user; + private $userFactory; /** * AdminUserAuthenticateBefore constructor. * * @param UserExpirationManager $userExpirationManager - * @param \Magento\User\Model\User $user + * @param \Magento\User\Model\UserFactory $userFactory */ public function __construct( \Magento\Security\Model\UserExpirationManager $userExpirationManager, - \Magento\User\Model\User $user + \Magento\User\Model\UserFactory $userFactory ) { $this->userExpirationManager = $userExpirationManager; - $this->user = $user; + $this->userFactory = $userFactory; } /** @@ -53,10 +53,11 @@ public function __construct( public function execute(Observer $observer) { $username = $observer->getEvent()->getUsername(); + $user = $this->userFactory->create(); /** @var \Magento\User\Model\User $user */ - $user = $this->user->loadByUsername($username); + $user->loadByUsername($username); - if ($user->getId() && $this->userExpirationManager->userIsExpired($user)) { + if ($user->getId() && $this->userExpirationManager->isUserExpired($user->getId())) { $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); throw new AuthenticationException( __( diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index 19c845a66bd86..dc116d059867a 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -79,7 +79,7 @@ public function setUp() $this->userExpirationManagerMock = $this->createPartialMock( \Magento\Security\Model\UserExpirationManager::class, - ['userIsExpired'] + ['isUserExpired'] ); $this->userMock = $this->createMock(\Magento\User\Model\User::class); @@ -183,6 +183,7 @@ public function testAroundProlongSessionIsActiveUserIsExpired() return $result; }; + $adminUserId = '12345'; $this->currentSessionMock->expects($this->once()) ->method('isLoggedInStatus') ->willReturn(true); @@ -191,9 +192,13 @@ public function testAroundProlongSessionIsActiveUserIsExpired() ->method('getUser') ->willReturn($this->userMock); + $this->userMock->expects($this->once()) + ->method('getId') + ->willReturn($adminUserId); + $this->userExpirationManagerMock->expects($this->once()) - ->method('userIsExpired') - ->with($this->userMock) + ->method('isUserExpired') + ->with($adminUserId) ->willReturn(true); $this->authSessionMock->expects($this->once()) @@ -225,6 +230,7 @@ public function testAroundProlongSessionIsActive() return $result; }; + $adminUserId = '12345'; $this->currentSessionMock->expects($this->any()) ->method('isLoggedInStatus') ->willReturn(true); @@ -233,9 +239,13 @@ public function testAroundProlongSessionIsActive() ->method('getUser') ->willReturn($this->userMock); + $this->userMock->expects($this->once()) + ->method('getId') + ->willReturn($adminUserId); + $this->userExpirationManagerMock->expects($this->once()) - ->method('userIsExpired') - ->with($this->userMock) + ->method('isUserExpired') + ->with($adminUserId) ->willReturn(false); $this->adminSessionsManagerMock->expects($this->any()) diff --git a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php similarity index 79% rename from app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php rename to app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php index b3aafa7cdfbab..0ae58756d3f77 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/BeforeAdminUserAuthenticateTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php @@ -12,7 +12,7 @@ * * @package Magento\Security\Test\Unit\Observer */ -class BeforeAdminUserAuthenticateTest extends \PHPUnit\Framework\TestCase +class AdminUserAuthenticateBeforeTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager @@ -29,6 +29,11 @@ class BeforeAdminUserAuthenticateTest extends \PHPUnit\Framework\TestCase */ private $userMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\User\Model\UserFactory + */ + private $userFactoryMock; + /** * @var \Magento\Security\Observer\AdminUserAuthenticateBefore */ @@ -60,14 +65,15 @@ protected function setUp() $this->userExpirationManagerMock = $this->createPartialMock( \Magento\Security\Model\UserExpirationManager::class, - ['userIsExpired', 'deactivateExpiredUsers'] + ['isUserExpired', 'deactivateExpiredUsers'] ); + $this->userFactoryMock = $this->createPartialMock(\Magento\User\Model\UserFactory::class, ['create']); $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['loadByUsername', 'getId']); $this->observer = $this->objectManager->getObject( \Magento\Security\Observer\AdminUserAuthenticateBefore::class, [ 'userExpirationManager' => $this->userExpirationManagerMock, - 'user' => $this->userMock, + 'userFactory' => $this->userFactoryMock, ] ); $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); @@ -85,17 +91,18 @@ protected function setUp() */ public function testWithExpiredUser() { - $adminUserId = 123; + $adminUserId = '123'; $username = 'testuser'; $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); $this->eventMock->expects(static::once())->method('getUsername')->willReturn($username); - $this->userMock->expects(static::once())->method('loadByUsername')->willReturn($this->userMock); + $this->userFactoryMock->expects(static::once())->method('create')->willReturn($this->userMock); + $this->userMock->expects(static::once())->method('loadByUsername')->willReturnSelf(); $this->userExpirationManagerMock->expects(static::once()) - ->method('userIsExpired') - ->with($this->userMock) + ->method('isUserExpired') + ->with($adminUserId) ->willReturn(true); - $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($adminUserId); + $this->userMock->expects(static::exactly(3))->method('getId')->willReturn($adminUserId); $this->userExpirationManagerMock->expects(static::once()) ->method('deactivateExpiredUsers') ->with([$adminUserId]) @@ -105,15 +112,16 @@ public function testWithExpiredUser() public function testWithNonExpiredUser() { - $adminUserId = 123; + $adminUserId = '123'; $username = 'testuser'; $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); $this->eventMock->expects(static::once())->method('getUsername')->willReturn($username); - $this->userMock->expects(static::once())->method('loadByUsername')->willReturn($this->userMock); - $this->userMock->expects(static::once())->method('getId')->willReturn($adminUserId); + $this->userFactoryMock->expects(static::once())->method('create')->willReturn($this->userMock); + $this->userMock->expects(static::once())->method('loadByUsername')->willReturnSelf(); + $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($adminUserId); $this->userExpirationManagerMock->expects(static::once()) - ->method('userIsExpired') - ->with($this->userMock) + ->method('isUserExpired') + ->with($adminUserId) ->willReturn(false); $this->observer->execute($this->eventObserverMock); } diff --git a/app/code/Magento/User/etc/db_schema_whitelist.json b/app/code/Magento/User/etc/db_schema_whitelist.json index c214cae477cd7..2af77c0d8455b 100644 --- a/app/code/Magento/User/etc/db_schema_whitelist.json +++ b/app/code/Magento/User/etc/db_schema_whitelist.json @@ -42,4 +42,4 @@ "ADMIN_PASSWORDS_USER_ID_ADMIN_USER_USER_ID": true } } -} +} \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 64829566ea3b4..1d41771416c87 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -9,7 +9,7 @@ /** * Tests for \Magento\Security\Model\UserExpirationManager - * + * @magentoAppArea adminhtml * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase @@ -42,8 +42,6 @@ class UserExpirationManagerTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->objectManager->get(\Magento\Framework\Config\ScopeInterface::class) - ->setCurrentScope(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); $this->auth = $this->objectManager->create(\Magento\Backend\Model\Auth::class); $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); $this->adminSessionInfo = $this->objectManager->create(\Magento\Security\Model\AdminSessionInfo::class); @@ -59,7 +57,7 @@ public function testUserIsExpired() { $adminUserNameFromFixture = 'adminUserExpired'; $user = $this->loadUserByUsername($adminUserNameFromFixture); - static::assertTrue($this->userExpirationManager->userIsExpired($user)); + static::assertTrue($this->userExpirationManager->isUserExpired($user->getId())); } /** From bcef5909ac75be4762e08a8bedbcd7e9cce14863 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 30 Sep 2019 19:00:01 -0400 Subject: [PATCH 47/70] replace changes in User module with plugin for admin user form (magento/magento2#22833: Short-term admin accounts) --- .../Security/Model/Plugin/AdminUserForm.php | 71 +++++++++++++++++++ .../Magento/Security/etc/adminhtml/di.xml | 3 + .../Magento/User/Block/User/Edit/Tab/Main.php | 19 ----- 3 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 app/code/Magento/Security/Model/Plugin/AdminUserForm.php diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php new file mode 100644 index 0000000000000..2ce8cce115dca --- /dev/null +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Security\Model\Plugin; + +/** + * Add the `expires_at` form field to the User main form. + * + * @package Magento\Security\Model\Plugin + */ +class AdminUserForm +{ + + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + private $localeDate; + + /** + * UserForm constructor. + * + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + */ + public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate) + { + $this->localeDate = $localeDate; + } + + /** + * Add the `expires_at` field to the admin user edit form. + * @param \Magento\User\Block\User\Edit\Tab\Main $subject + * @param \Closure $proceed + * @return mixed + */ + public function aroundGetFormHtml( + \Magento\User\Block\User\Edit\Tab\Main $subject, + \Closure $proceed + ) { + /** @var \Magento\Framework\Data\Form $form */ + $form = $subject->getForm(); + if (is_object($form)) { + $dateFormat = $this->localeDate->getDateFormat( + \IntlDateFormatter::MEDIUM + ); + $timeFormat = $this->localeDate->getTimeFormat( + \IntlDateFormatter::MEDIUM + ); + $fieldset = $form->getElement('base_fieldset'); + $fieldset->addField( + 'expires_at', + 'date', + [ + 'name' => 'expires_at', + 'label' => __('Expiration Date'), + 'title' => __('Expiration Date'), + 'date_format' => $dateFormat, + 'time_format' => $timeFormat, + 'class' => 'validate-date', + ] + ); + + $subject->setForm($form); + } + + return $proceed(); + } +} diff --git a/app/code/Magento/Security/etc/adminhtml/di.xml b/app/code/Magento/Security/etc/adminhtml/di.xml index 8cf5b610ef3c5..388a1eac742a5 100644 --- a/app/code/Magento/Security/etc/adminhtml/di.xml +++ b/app/code/Magento/Security/etc/adminhtml/di.xml @@ -31,4 +31,7 @@ </argument> </arguments> </type> + <type name="Magento\User\Block\User\Edit\Tab\Main"> + <plugin name="user_expiration_user_form_field" type="Magento\Security\Model\Plugin\AdminUserForm"/> + </type> </config> diff --git a/app/code/Magento/User/Block/User/Edit/Tab/Main.php b/app/code/Magento/User/Block/User/Edit/Tab/Main.php index c92714ec1eb5a..27e00483733d0 100644 --- a/app/code/Magento/User/Block/User/Edit/Tab/Main.php +++ b/app/code/Magento/User/Block/User/Edit/Tab/Main.php @@ -170,25 +170,6 @@ protected function _prepareForm() ); } - $dateFormat = $this->_localeDate->getDateFormat( - \IntlDateFormatter::MEDIUM - ); - $timeFormat = $this->_localeDate->getTimeFormat( - \IntlDateFormatter::MEDIUM - ); - $baseFieldset->addField( - 'expires_at', - 'date', - [ - 'name' => 'expires_at', - 'label' => __('Expiration Date'), - 'title' => __('Expiration Date'), - 'date_format' => $dateFormat, - 'time_format' => $timeFormat, - 'class' => 'validate-date', - ] - ); - $baseFieldset->addField('user_roles', 'hidden', ['name' => 'user_roles', 'id' => '_user_roles']); $currentUserVerificationFieldset = $form->addFieldset( From 627273b5c49f9298b5094de9e62370bbbc85eb4f Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 1 Oct 2019 10:27:35 -0400 Subject: [PATCH 48/70] phpcs fixes (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/Plugin/AdminUserForm.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index 2ce8cce115dca..5eca5f3af3b4c 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -32,6 +32,7 @@ public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface /** * Add the `expires_at` field to the admin user edit form. + * * @param \Magento\User\Block\User\Edit\Tab\Main $subject * @param \Closure $proceed * @return mixed From 6b8e89e4b1541fdd0eb603a72bfcea1020d5467d Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 7 Oct 2019 20:23:23 -0400 Subject: [PATCH 49/70] fix invalid date input and add translations (magento/magento2#22833: Short-term admin accounts) --- .../Model/UserExpiration/Validator.php | 14 +++++++++----- .../Model/UserExpiration/ValidatorTest.php | 19 +++++++++++++++---- app/code/Magento/Security/i18n/en_US.csv | 2 ++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index b132c122cbc34..c71b2ecb849a2 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -51,11 +51,15 @@ public function isValid($value) $expiresAt = $value; $label = 'Expiration date'; if (\Zend_Validate::is($expiresAt, 'NotEmpty')) { - $currentTime = $this->dateTime->gmtTimestamp(); - $utcExpiresAt = $this->timezone->convertConfigTimeToUtc($expiresAt); - $expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp(); - if ($expiresAt < $currentTime) { - $messages['expires_at'] = __('"%1" must be later than the current date.', $label); + if (strtotime($expiresAt)) { + $currentTime = $this->dateTime->gmtTimestamp(); + $utcExpiresAt = $this->timezone->convertConfigTimeToUtc($expiresAt); + $expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp(); + if ($expiresAt < $currentTime) { + $messages['expires_at'] = __('"%1" must be later than the current date.', $label); + } + } else { + $messages['expires_at'] = __('"%1" is not a valid date.', $label); } } $this->_addMessages($messages); diff --git a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php index b000ebbac96cb..1cc53e76ee969 100644 --- a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php @@ -46,13 +46,24 @@ protected function setUp() ); } + public function testWithInvalidDate() + { + $expireDate = 'invalid_date'; + + static::assertFalse($this->validator->isValid($expireDate)); + static::assertContains( + '"Expiration date" is not a valid date.', + $this->validator->getMessages() + ); + } + public function testWithPastDate() { /** @var \DateTime|\PHPUnit_Framework_MockObject_MockObject $dateObject */ $dateObject = $this->createMock(\DateTime::class); $this->timezoneMock->expects(static::once()) - ->method('date') - ->will(static::returnValue($dateObject)); + ->method('date') + ->will(static::returnValue($dateObject)); $currentDate = new \DateTime(); $currentDate = $currentDate->getTimestamp(); @@ -74,8 +85,8 @@ public function testWithFutureDate() /** @var \DateTime|\PHPUnit_Framework_MockObject_MockObject $dateObject */ $dateObject = $this->createMock(\DateTime::class); $this->timezoneMock->expects(static::once()) - ->method('date') - ->will(static::returnValue($dateObject)); + ->method('date') + ->will(static::returnValue($dateObject)); $currentDate = new \DateTime(); $currentDate = $currentDate->getTimestamp(); $expireDate = new \DateTime(); diff --git a/app/code/Magento/Security/i18n/en_US.csv b/app/code/Magento/Security/i18n/en_US.csv index 4329e0747d08e..3d5bb6e8b59d4 100644 --- a/app/code/Magento/Security/i18n/en_US.csv +++ b/app/code/Magento/Security/i18n/en_US.csv @@ -27,3 +27,5 @@ None,None "Min Time Between Password Reset Requests","Min Time Between Password Reset Requests" "Delay in minutes between password reset requests. Use 0 to disable.","Delay in minutes between password reset requests. Use 0 to disable." """%1"" must be later than the current date.","""%1"" must be later than the current date." +"User Expiration","User Expiration" +"""%1"" is not a valid date.","""%1"" is not a valid date." From 9e6f316cd1e9cc11310bb15225cf7b208ff6cfc1 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 8 Oct 2019 18:23:56 -0400 Subject: [PATCH 50/70] fix invalid date input in mftf test (magento/magento2#22833: Short-term admin accounts) --- .../Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index ee8dfdb5cb085..f05863b387b1a 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -17,7 +17,7 @@ <description value="Try to login as a user with an invalid expiration date."/> <testCaseId value=""/> <severity value="CRITICAL"/> - <group value="security"/> + <group value="debug"/> </annotations> <before> @@ -25,12 +25,14 @@ </before> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+2 minute" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <generateDate date="+2 minute" format="M d, Y h:i A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> <grabValueFrom selector="{{AdminNewUserFormSection.username}}" stepKey="grabUsername"/> <grabValueFrom selector="{{AdminNewUserFormSection.password}}" stepKey="grabPassword"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewUserFormSection.userInfoTab}}" stepKey="openUserInfoTab"/> <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess"/> <actionGroup ref="logout" stepKey="logout"/> <wait time="120" stepKey="waitForUserToExpire"/> From 20f2f0e052ece2df3815977ffb9dc9cc72d6616f Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Wed, 9 Oct 2019 09:54:49 -0500 Subject: [PATCH 51/70] Fixed test group name --- .../Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index f05863b387b1a..a752c484238fa 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -17,7 +17,7 @@ <description value="Try to login as a user with an invalid expiration date."/> <testCaseId value=""/> <severity value="CRITICAL"/> - <group value="debug"/> + <group value="security"/> </annotations> <before> From 1b1c12ed064f350a58a509421c4f7437be7f24eb Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 13 Oct 2019 18:51:39 -0400 Subject: [PATCH 52/70] fix dates in MFTF test; update admin user form plugin to set the user expiration value (#22833) --- .../Security/Model/Plugin/AdminUserForm.php | 38 +++++- .../Security/Observer/AfterAdminUserLoad.php | 62 --------- ...CreateNewUserWithInvalidExpirationTest.xml | 3 +- ...inCreateNewUserWithValidExpirationTest.xml | 14 +- ...minLoginAdminUserWithInvalidExpiration.xml | 2 +- ...AdminLoginAdminUserWithValidExpiration.xml | 3 +- .../Unit/Observer/AfterAdminUserLoadTest.php | 120 ------------------ app/code/Magento/Security/etc/events.xml | 3 - .../Observer/AfterAdminUserLoadTest.php | 46 ------- 9 files changed, 54 insertions(+), 237 deletions(-) delete mode 100644 app/code/Magento/Security/Observer/AfterAdminUserLoad.php delete mode 100644 app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index 5eca5f3af3b4c..d61de5d67adfa 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -7,6 +7,8 @@ namespace Magento\Security\Model\Plugin; +use Magento\Security\Model\UserExpiration; + /** * Add the `expires_at` form field to the User main form. * @@ -20,14 +22,31 @@ class AdminUserForm */ private $localeDate; + /** + * @var \Magento\Security\Model\ResourceModel\UserExpiration + */ + private $userExpirationResource; + + /** + * @var UserExpirationFactory + */ + private $userExpirationFactory; + /** * UserForm constructor. * * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param UserExpiration $userExpiration + * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource */ - public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate) - { + public function __construct( + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, + \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + ) { $this->localeDate = $localeDate; + $this->userExpirationResource = $userExpirationResource; + $this->userExpirationFactory = $userExpirationFactory; } /** @@ -51,6 +70,12 @@ public function aroundGetFormHtml( \IntlDateFormatter::MEDIUM ); $fieldset = $form->getElement('base_fieldset'); + $userIdField = $fieldset->getElements()->searchById('user_id'); + $userExpirationValue = null; + if ($userIdField) { + $userId = $userIdField->getValue(); + $userExpirationValue = $this->loadUserExpirationByUserId($userId); + } $fieldset->addField( 'expires_at', 'date', @@ -61,6 +86,7 @@ public function aroundGetFormHtml( 'date_format' => $dateFormat, 'time_format' => $timeFormat, 'class' => 'validate-date', + 'value' => $userExpirationValue, ] ); @@ -69,4 +95,12 @@ public function aroundGetFormHtml( return $proceed(); } + + private function loadUserExpirationByUserId($userId) + { + /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpiration = $this->userExpirationFactory->create(); + $this->userExpirationResource->load($userExpiration, $userId); + return $userExpiration->getExpiresAt(); + } } diff --git a/app/code/Magento/Security/Observer/AfterAdminUserLoad.php b/app/code/Magento/Security/Observer/AfterAdminUserLoad.php deleted file mode 100644 index 133b26c2c01a2..0000000000000 --- a/app/code/Magento/Security/Observer/AfterAdminUserLoad.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Security\Observer; - -use Magento\Framework\Event\Observer; -use Magento\Framework\Event\ObserverInterface; - -/** - * Load UserExpiration on admin user. Only needed in the admin to load the expires_at when editing users. - */ -class AfterAdminUserLoad implements ObserverInterface -{ - /** - * @var \Magento\Security\Model\UserExpirationFactory - */ - private $userExpirationFactory; - - /** - * @var \Magento\Security\Model\ResourceModel\UserExpiration - */ - private $userExpirationResource; - - /** - * AfterAdminUserLoad constructor. - * - * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory - * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource - */ - public function __construct( - \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, - \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource - ) { - - $this->userExpirationFactory = $userExpirationFactory; - $this->userExpirationResource = $userExpirationResource; - } - - /** - * Set the user expiration date onto the user. - * - * @param Observer $observer - * @return void - */ - public function execute(Observer $observer) - { - /* @var $user \Magento\User\Model\User */ - $user = $observer->getEvent()->getObject(); - if ($user->getId()) { - /** @var \Magento\Security\Model\UserExpiration $userExpiration */ - $userExpiration = $this->userExpirationFactory->create(); - $this->userExpirationResource->load($userExpiration, $user->getId()); - if ($userExpiration->getExpiresAt()) { - $user->setExpiresAt($userExpiration->getExpiresAt()); - } - } - } -} diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml index 6bcd7be784f88..b585a21dd743b 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml @@ -16,6 +16,7 @@ <testCaseId value="" /> <severity value="CRITICAL"/> <group value="security"/> + <group value="debug"/> </annotations> <before> @@ -26,7 +27,7 @@ </after> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="-5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <generateDate date="-5 day" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml index f798865ab2279..8a93000034bf7 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml @@ -16,6 +16,7 @@ <testCaseId value="" /> <severity value="CRITICAL"/> <group value="security"/> + <group value="debug"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -25,10 +26,21 @@ </after> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <generateDate date="+5 day" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess" /> + + <actionGroup ref="AdminOpenUserEditPageActionGroup" stepKey="openUserEditPage"> + <argument name="user" value="NewAdminUser"/> + </actionGroup> + + <seeInField userInput="{$expiresDateTime}" selector="input#user_expires_at" stepKey="verifyUserExpirationInField"/> + + <actionGroup ref="AdminDeleteCustomUserActionGroup" stepKey="deleteUser"> + <argument name="user" value="NewAdminUser"/> + </actionGroup> + <!-- TODO: delete created user --> </test> </tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index a752c484238fa..c464633554f5c 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -25,7 +25,7 @@ </before> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+2 minute" format="M d, Y h:i A" stepKey="expiresDateTime"/> + <generateDate date="+2 minute" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml index f368a71d84d85..f577790871fcd 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml @@ -18,6 +18,7 @@ <testCaseId value=""/> <severity value="CRITICAL"/> <group value="security"/> + <group value="debug"/> </annotations> <before> @@ -28,7 +29,7 @@ </after> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+5 day" format="M d, Y h:i:s" stepKey="expiresDateTime"/> + <generateDate date="+5 day" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> diff --git a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php deleted file mode 100644 index dfcbcad5780b8..0000000000000 --- a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserLoadTest.php +++ /dev/null @@ -1,120 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Security\Test\Unit\Observer; - -/** - * Test class for \Magento\Security\Observer\AfterAdminUserLoad - */ -class AfterAdminUserLoadTest extends \PHPUnit\Framework\TestCase -{ - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\UserExpirationFactory - */ - private $userExpirationFactoryMock; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\ResourceModel\UserExpiration - */ - private $userExpirationResourceMock; - - /** - * @var \Magento\Security\Observer\AfterAdminUserLoad - */ - private $observer; - - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - private $objectManager; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Framework\Event\Observer - */ - private $eventObserverMock; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Framework\Event - */ - private $eventMock; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\User\Model\User - */ - private $userMock; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\UserExpiration - */ - private $userExpirationMock; - - protected function setUp() - { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->userExpirationFactoryMock = $this->createMock(\Magento\Security\Model\UserExpirationFactory::class); - $this->userExpirationResourceMock = $this->createPartialMock( - \Magento\Security\Model\ResourceModel\UserExpiration::class, - ['load'] - ); - $this->observer = $this->objectManager->getObject( - \Magento\Security\Observer\AfterAdminUserLoad::class, - [ - 'userExpirationFactory' => $this->userExpirationFactoryMock, - 'userExpirationResource' => $this->userExpirationResourceMock, - ] - ); - - $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); - $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getObject']); - $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['getId', 'setExpiresAt']); - $this->userExpirationMock = $this->createPartialMock( - \Magento\Security\Model\UserExpiration::class, - ['getExpiresAt'] - ); - } - - public function testWithExpiredUser() - { - $userId = '123'; - $testDate = new \DateTime(); - $testDate->modify('+10 days'); - $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); - $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); - $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); - $this->userExpirationFactoryMock->expects(static::once()) - ->method('create') - ->willReturn($this->userExpirationMock); - $this->userExpirationResourceMock->expects(static::once()) - ->method('load') - ->willReturn($this->userExpirationMock); - $this->userExpirationMock->expects(static::exactly(2)) - ->method('getExpiresAt') - ->willReturn($testDate->format('Y-m-d H:i:s')); - $this->userMock->expects(static::once()) - ->method('setExpiresAt') - ->willReturn($this->userMock); - $this->observer->execute($this->eventObserverMock); - } - - public function testWithNonExpiredUser() - { - $userId = '123'; - $this->eventObserverMock->expects(static::once())->method('getEvent')->willReturn($this->eventMock); - $this->eventMock->expects(static::once())->method('getObject')->willReturn($this->userMock); - $this->userMock->expects(static::exactly(2))->method('getId')->willReturn($userId); - $this->userExpirationFactoryMock->expects(static::once())->method('create') - ->willReturn($this->userExpirationMock); - $this->userExpirationResourceMock->expects(static::once())->method('load') - ->willReturn($this->userExpirationMock); - $this->userExpirationMock->expects(static::once()) - ->method('getExpiresAt') - ->willReturn(null); - $this->observer->execute($this->eventObserverMock); - } -} diff --git a/app/code/Magento/Security/etc/events.xml b/app/code/Magento/Security/etc/events.xml index 9db9a67947ca8..f85cfc9387c48 100644 --- a/app/code/Magento/Security/etc/events.xml +++ b/app/code/Magento/Security/etc/events.xml @@ -10,9 +10,6 @@ <event name="admin_user_save_after"> <observer name="add_user_expiration" instance="Magento\Security\Observer\AfterAdminUserSave"/> </event> - <event name="admin_user_load_after"> - <observer name="add_user_expiration" instance="Magento\Security\Observer\AfterAdminUserLoad"/> - </event> <event name="admin_user_authenticate_before"> <observer name="check_user_expiration" instance="Magento\Security\Observer\AdminUserAuthenticateBefore"/> </event> diff --git a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php b/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php deleted file mode 100644 index fa552f04b9f2e..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Security/Observer/AfterAdminUserLoadTest.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Security\Observer; - -use Magento\TestFramework\Helper\Bootstrap; - -/** - * Test for \Magento\Security\Observer\AfterAdminUserLoad * - */ -class AfterAdminUserLoadTest extends \PHPUnit\Framework\TestCase -{ - - /** - * @magentoDataFixture Magento/Security/_files/expired_users.php - */ - public function testWithUserWithExpiration() - { - $adminUserNameFromFixture = 'adminUserExpired'; - /** @var \Magento\User\Model\User $user */ - $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername($adminUserNameFromFixture); - $userId = $user->getId(); - $loadedUser = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $loadedUser->load($userId); - static::assertNotNull($loadedUser->getExpiresAt()); - } - - /** - * @magentoDataFixture Magento/User/_files/dummy_user.php - */ - public function testWithNonExpiredUser() - { - $adminUserNameFromFixture = 'dummy_username'; - $user = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $user->loadByUsername($adminUserNameFromFixture); - $userId = $user->getId(); - $loadedUser = Bootstrap::getObjectManager()->create(\Magento\User\Model\User::class); - $loadedUser->load($userId); - static::assertNull($loadedUser->getExpiresAt()); - } -} From 91e0604e0a3b95472dfeb8e0561afa0156c36056 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sun, 13 Oct 2019 18:58:54 -0400 Subject: [PATCH 53/70] remove test debug group (#22833) --- .../Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml | 1 - .../Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml | 3 --- .../Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml | 1 - 3 files changed, 5 deletions(-) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml index b585a21dd743b..919d2523927fa 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml @@ -16,7 +16,6 @@ <testCaseId value="" /> <severity value="CRITICAL"/> <group value="security"/> - <group value="debug"/> </annotations> <before> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml index 8a93000034bf7..a00872d848472 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml @@ -16,7 +16,6 @@ <testCaseId value="" /> <severity value="CRITICAL"/> <group value="security"/> - <group value="debug"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -35,12 +34,10 @@ <actionGroup ref="AdminOpenUserEditPageActionGroup" stepKey="openUserEditPage"> <argument name="user" value="NewAdminUser"/> </actionGroup> - <seeInField userInput="{$expiresDateTime}" selector="input#user_expires_at" stepKey="verifyUserExpirationInField"/> <actionGroup ref="AdminDeleteCustomUserActionGroup" stepKey="deleteUser"> <argument name="user" value="NewAdminUser"/> </actionGroup> - <!-- TODO: delete created user --> </test> </tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml index f577790871fcd..c33cfb36d209a 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml @@ -18,7 +18,6 @@ <testCaseId value=""/> <severity value="CRITICAL"/> <group value="security"/> - <group value="debug"/> </annotations> <before> From 47dfddbd858a3ad5b12ebb709e12a2a1ff65633c Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 14 Oct 2019 15:36:57 -0400 Subject: [PATCH 54/70] delete users after tests in mftf (magento/magento2#22833: Short-term admin accounts) --- .../Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml | 5 +++++ .../Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml index c464633554f5c..bdea845c81a56 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithInvalidExpiration.xml @@ -42,5 +42,10 @@ </actionGroup> <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="checkLoginMessage" /> + <!-- delete user --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCustomUserActionGroup" stepKey="deleteUser"> + <argument name="user" value="NewAdminUser" /> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml index c33cfb36d209a..12bba27f21269 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminLoginAdminUserWithValidExpiration.xml @@ -42,5 +42,10 @@ </actionGroup> <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> + <actionGroup ref="logout" stepKey="logoutAsUserWithValidExpiration"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCustomUserActionGroup" stepKey="deleteUser"> + <argument name="user" value="NewAdminUser"/> + </actionGroup> </test> </tests> From c6f9e6bbe269ff530a606acba5be70f398f082cf Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 14 Oct 2019 18:20:20 -0400 Subject: [PATCH 55/70] fix phpdocs (magento/magento2#22833: Short-term admin accounts) --- .../Magento/Security/Model/Plugin/AdminUserForm.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index d61de5d67adfa..f1777f182564a 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -28,7 +28,7 @@ class AdminUserForm private $userExpirationResource; /** - * @var UserExpirationFactory + * @var \Magento\Security\Model\UserExpirationFactory */ private $userExpirationFactory; @@ -36,7 +36,7 @@ class AdminUserForm * UserForm constructor. * * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param UserExpiration $userExpiration + * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource */ public function __construct( @@ -96,6 +96,12 @@ public function aroundGetFormHtml( return $proceed(); } + /** + * Loads a user expiration record by user ID. + * + * @param $userId string + * @return string + */ private function loadUserExpirationByUserId($userId) { /** @var \Magento\Security\Model\UserExpiration $userExpiration */ From df0c97c30036d6989e76c95132750afb479a69bf Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 15 Oct 2019 12:02:40 -0400 Subject: [PATCH 56/70] remove unused import and fix phpdocs (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/Plugin/AdminUserForm.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index f1777f182564a..d6276519578f9 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -7,8 +7,6 @@ namespace Magento\Security\Model\Plugin; -use Magento\Security\Model\UserExpiration; - /** * Add the `expires_at` form field to the User main form. * @@ -99,7 +97,7 @@ public function aroundGetFormHtml( /** * Loads a user expiration record by user ID. * - * @param $userId string + * @param string $userId * @return string */ private function loadUserExpirationByUserId($userId) From babc965cfaefc99601f580e1fb8cf8aab82f1788 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 19 Oct 2019 12:28:26 -0400 Subject: [PATCH 57/70] deactivate expired users on session prolong (#22833) --- .../Security/Model/Plugin/AuthSession.php | 8 ++- .../AdminNavigateWhileUserExpiredTest.xml | 53 +++++++++++++++++++ .../Unit/Model/Plugin/AuthSessionTest.php | 20 ++++--- .../Security/Model/Plugin/AuthSessionTest.php | 3 ++ 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index 910d0058e7021..05c44367caaee 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -71,8 +71,12 @@ public function __construct( */ public function aroundProlong(Session $session, \Closure $proceed) { - if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus() || - $this->userExpirationManager->isUserExpired($session->getUser()->getId())) { + if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus()) { + $session->destroy(); + $this->addUserLogoutNotification(); + return null; + } elseif ($this->userExpirationManager->isUserExpired($session->getUser()->getId())) { + $this->userExpirationManager->deactivateExpiredUsers([$session->getUser()->getId()]); $session->destroy(); $this->addUserLogoutNotification(); return null; diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml new file mode 100644 index 0000000000000..f8795454fdd16 --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + + <test name="AdminNavigateWhileUserExpiredTest"> + <annotations> + <features value="Security"/> + <stories value="Navigate to an admin page after user expiration date passes."/> + <title value="Navigate to an admin page after user expiration date passes"/> + <description value="Navigate to an admin page after user expiration date passes."/> + <testCaseId value="" /> + <severity value="CRITICAL"/> + <group value="security"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create user --> + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + <generateDate date="+2 minute" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> + <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> + <argument name="expires_at" value="{$expiresDateTime}"/> + </actionGroup> + <grabValueFrom selector="{{AdminNewUserFormSection.username}}" stepKey="grabUsername"/> + <grabValueFrom selector="{{AdminNewUserFormSection.password}}" stepKey="grabPassword"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewUserFormSection.userInfoTab}}" stepKey="openUserInfoTab"/> + <actionGroup ref="AdminSaveUserSuccessActionGroup" stepKey="saveNewUserWithValidExpirationSuccess"/> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Login as that user --> + <actionGroup ref="LoginAdminWithCredentialsActionGroup" stepKey="loginAsNewAdmin"> + <argument name="adminUser" value="{$grabUsername}"/> + <argument name="adminPassword" value="{$grabPassword}"/> + </actionGroup> + <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> + <wait time="120" stepKey="waitForUserToExpire"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> + <!-- Confirm that user is logged out --> + <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> + + <!-- Delete created user --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCustomUserActionGroup" stepKey="deleteUser"> + <argument name="user" value="NewAdminUser"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index dc116d059867a..5bcda352c7af9 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -79,7 +79,7 @@ public function setUp() $this->userExpirationManagerMock = $this->createPartialMock( \Magento\Security\Model\UserExpirationManager::class, - ['isUserExpired'] + ['isUserExpired', 'deactivateExpiredUsers'] ); $this->userMock = $this->createMock(\Magento\User\Model\User::class); @@ -188,27 +188,31 @@ public function testAroundProlongSessionIsActiveUserIsExpired() ->method('isLoggedInStatus') ->willReturn(true); - $this->authSessionMock->expects($this->once()) + $this->authSessionMock->expects($this->exactly(2)) ->method('getUser') ->willReturn($this->userMock); - $this->userMock->expects($this->once()) + $this->userMock->expects($this->exactly(2)) ->method('getId') ->willReturn($adminUserId); + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('isAjax') + ->willReturn(false); + $this->userExpirationManagerMock->expects($this->once()) ->method('isUserExpired') ->with($adminUserId) ->willReturn(true); + $this->userExpirationManagerMock->expects($this->once()) + ->method('deactivateExpiredUsers') + ->with([$adminUserId]); + $this->authSessionMock->expects($this->once()) ->method('destroy'); - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('isAjax') - ->willReturn(false); - $this->adminSessionsManagerMock->expects($this->once()) ->method('getLogoutReasonMessage') ->willReturn($errorMessage); diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 27b085312186e..598cb6daafae1 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -156,6 +156,7 @@ public function testProcessProlongWithExpiredUser() $expireDate = new \DateTime(); $expireDate->modify('-10 days'); + /** @var \Magento\User\Model\User $user */ $user = $this->objectManager->create(\Magento\User\Model\User::class); $user->loadByUsername(\Magento\TestFramework\Bootstrap::ADMIN_NAME); $userExpirationFactory = $this->objectManager->create(\Magento\Security\Model\UserExpirationFactory::class); @@ -178,5 +179,7 @@ public function testProcessProlongWithExpiredUser() $this->adminSessionInfo->load($sessionId, 'session_id'); $this->authSession->prolong(); static::assertFalse($this->auth->isLoggedIn()); + $user->reload(); + static::assertFalse((bool)$user->getIsActive()); } } From 4c4149fdfc4d658a8bbb6649eb9f4b89828296d0 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 19 Oct 2019 12:59:15 -0400 Subject: [PATCH 58/70] add copyright (#22833) --- .../Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml index f8795454fdd16..c4603a88c56c4 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml @@ -1,4 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> From 33a5d36e583c3d6a3a2a0cbb1fa592807ae930a6 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Fri, 25 Oct 2019 09:57:40 -0400 Subject: [PATCH 59/70] split out deactivateExpiredUsers into two methods suitable for calling without input parameters in cron (#22833) --- .../Security/Model/Plugin/AuthSession.php | 2 +- .../Security/Model/UserExpirationManager.php | 29 +++++++++++++++---- .../Observer/AdminUserAuthenticateBefore.php | 2 +- .../Unit/Model/Plugin/AuthSessionTest.php | 4 +-- .../AdminUserAuthenticateBeforeTest.php | 4 +-- .../Model/UserExpirationManagerTest.php | 4 +-- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index 05c44367caaee..6dc5e796d8950 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -76,7 +76,7 @@ public function aroundProlong(Session $session, \Closure $proceed) $this->addUserLogoutNotification(); return null; } elseif ($this->userExpirationManager->isUserExpired($session->getUser()->getId())) { - $this->userExpirationManager->deactivateExpiredUsers([$session->getUser()->getId()]); + $this->userExpirationManager->deactivateExpiredUsersById([$session->getUser()->getId()]); $session->destroy(); $this->addUserLogoutNotification(); return null; diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index 9b26935c1baf2..7043a559ca1d4 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -76,17 +76,34 @@ public function __construct( /** * Deactivate expired user accounts and invalidate their sessions. - * - * @param array|null $userIds */ - public function deactivateExpiredUsers(?array $userIds = null): void + public function deactivateExpiredUsers(): void { /** @var ExpiredUsersCollection $expiredRecords */ $expiredRecords = $this->userExpirationCollectionFactory->create()->addActiveExpiredUsersFilter(); - if ($userIds != null) { - $expiredRecords->addUserIdsFilter($userIds); - } + $this->processExpiredUsers($expiredRecords); + } + + /** + * Deactivate specific expired users. + * + * @param array $userIds + */ + public function deactivateExpiredUsersById(array $userIds): void + { + $expiredRecords = $this->userExpirationCollectionFactory->create() + ->addActiveExpiredUsersFilter() + ->addUserIdsFilter($userIds); + $this->processExpiredUsers($expiredRecords); + } + /** + * Deactivate expired user accounts and invalidate their sessions. + * + * @param ExpiredUsersCollection $expiredRecords + */ + private function processExpiredUsers(ExpiredUsersCollection $expiredRecords): void + { if ($expiredRecords->getSize() > 0) { // get all active sessions for the users and set them to logged out /** @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection $currentSessions */ diff --git a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index 1ffdd1cd1d11e..61ce889e9722a 100644 --- a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -58,7 +58,7 @@ public function execute(Observer $observer) $user->loadByUsername($username); if ($user->getId() && $this->userExpirationManager->isUserExpired($user->getId())) { - $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + $this->userExpirationManager->deactivateExpiredUsersById([$user->getId()]); throw new AuthenticationException( __( 'The account sign-in was incorrect or your account is disabled temporarily. ' diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index 5bcda352c7af9..9870de8fc2cb4 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -79,7 +79,7 @@ public function setUp() $this->userExpirationManagerMock = $this->createPartialMock( \Magento\Security\Model\UserExpirationManager::class, - ['isUserExpired', 'deactivateExpiredUsers'] + ['isUserExpired', 'deactivateExpiredUsersById'] ); $this->userMock = $this->createMock(\Magento\User\Model\User::class); @@ -207,7 +207,7 @@ public function testAroundProlongSessionIsActiveUserIsExpired() ->willReturn(true); $this->userExpirationManagerMock->expects($this->once()) - ->method('deactivateExpiredUsers') + ->method('deactivateExpiredUsersById') ->with([$adminUserId]); $this->authSessionMock->expects($this->once()) diff --git a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php index 0ae58756d3f77..ecfd3e3759dd3 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php @@ -65,7 +65,7 @@ protected function setUp() $this->userExpirationManagerMock = $this->createPartialMock( \Magento\Security\Model\UserExpirationManager::class, - ['isUserExpired', 'deactivateExpiredUsers'] + ['isUserExpired', 'deactivateExpiredUsersById'] ); $this->userFactoryMock = $this->createPartialMock(\Magento\User\Model\UserFactory::class, ['create']); $this->userMock = $this->createPartialMock(\Magento\User\Model\User::class, ['loadByUsername', 'getId']); @@ -104,7 +104,7 @@ public function testWithExpiredUser() ->willReturn(true); $this->userMock->expects(static::exactly(3))->method('getId')->willReturn($adminUserId); $this->userExpirationManagerMock->expects(static::once()) - ->method('deactivateExpiredUsers') + ->method('deactivateExpiredUsersById') ->with([$adminUserId]) ->willReturn(null); $this->observer->execute($this->eventObserverMock); diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 1d41771416c87..9ab7287c414a6 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -71,7 +71,7 @@ public function testDeactivateExpiredUsersWithExpiredUser() $user = $this->loadUserByUsername($adminUsernameFromFixture); $sessionId = $this->authSession->getSessionId(); $this->expireUser($user); - $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + $this->userExpirationManager->deactivateExpiredUsersById([$user->getId()]); $this->adminSessionInfo->load($sessionId, 'session_id'); $user->reload(); $userExpirationModel = $this->loadExpiredUserModelByUser($user); @@ -90,7 +90,7 @@ public function testDeactivateExpiredUsersWithNonExpiredUser() $this->loginUser($adminUsernameFromFixture); $user = $this->loadUserByUsername($adminUsernameFromFixture); $sessionId = $this->authSession->getSessionId(); - $this->userExpirationManager->deactivateExpiredUsers([$user->getId()]); + $this->userExpirationManager->deactivateExpiredUsersById([$user->getId()]); $user->reload(); $userExpirationModel = $this->loadExpiredUserModelByUser($user); $this->adminSessionInfo->load($sessionId, 'session_id'); From 95fce13800748fe5b4e7c556fbd0302f69b1adaa Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 18 Jan 2020 15:37:41 -0500 Subject: [PATCH 60/70] fix static tests (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/Plugin/AdminUserForm.php | 2 -- .../Magento/Security/Model/Plugin/UserValidationRules.php | 2 -- app/code/Magento/Security/Model/UserExpiration/Validator.php | 4 +--- .../Magento/Security/Observer/AdminUserAuthenticateBefore.php | 2 -- .../Test/Unit/Model/Plugin/UserValidationRulesTest.php | 4 +--- .../Security/Test/Unit/Model/UserExpiration/ValidatorTest.php | 4 +--- .../Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php | 2 -- .../Security/Test/Unit/Observer/AfterAdminUserSaveTest.php | 2 -- 8 files changed, 3 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index d6276519578f9..106295d5774ff 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -9,8 +9,6 @@ /** * Add the `expires_at` form field to the User main form. - * - * @package Magento\Security\Model\Plugin */ class AdminUserForm { diff --git a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php index f400d962644d6..00d5bc13b65e8 100644 --- a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php +++ b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php @@ -9,8 +9,6 @@ /** * \Magento\User\Model\UserValidationRules decorator - * - * @package Magento\Security\Model\Plugin */ class UserValidationRules { diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index c71b2ecb849a2..46633db260692 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -10,9 +10,7 @@ use Magento\Framework\Validator\AbstractValidator; /** - * Class Validator - * - * @package Magento\Security\Model\Validator + * Validates that the expires_at field is later than the current date/time. */ class Validator extends AbstractValidator { diff --git a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index 61ce889e9722a..3c07285afcb32 100644 --- a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -14,8 +14,6 @@ /** * Check for expired users. - * - * @package Magento\Security\Observer */ class AdminUserAuthenticateBefore implements ObserverInterface { diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php index 4498e514e45d3..00b3356c2e11d 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/UserValidationRulesTest.php @@ -8,9 +8,7 @@ namespace Magento\Security\Test\Unit\Model\Plugin; /** - * Class UserValidationRulesTest - * - * @package Magento\Security\Test\Unit\Model\Plugin + * Test class for expiration date user validation rule. */ class UserValidationRulesTest extends \PHPUnit\Framework\TestCase { diff --git a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php index 1cc53e76ee969..28541231cb123 100644 --- a/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php @@ -10,9 +10,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** - * Class ExpiresAtValidatorTest - * - * @package Magento\User\Test\Unit\Model + * Test class for \Magento\Security\Model\UserExpiration\Validator. */ class ValidatorTest extends \PHPUnit\Framework\TestCase { diff --git a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php index ecfd3e3759dd3..403255cbb60f8 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php @@ -9,8 +9,6 @@ /** * Test for \Magento\Security\Observer\AdminUserAuthenticateBefore - * - * @package Magento\Security\Test\Unit\Observer */ class AdminUserAuthenticateBeforeTest extends \PHPUnit\Framework\TestCase { diff --git a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php index 439ec3f88548b..85505632e1eb6 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AfterAdminUserSaveTest.php @@ -9,8 +9,6 @@ /** * Test class for \Magento\Security\Observer\AfterAdminUserSave - * - * @package Magento\Security\Test\Unit\Observer */ class AfterAdminUserSaveTest extends \PHPUnit\Framework\TestCase { From 932559d17549ec30d8b27c86be0439a355ff33a7 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 18 Jan 2020 16:05:48 -0500 Subject: [PATCH 61/70] fix static tests (magento/magento2#22833: Short-term admin accounts) --- .../Model/ResourceModel/UserExpiration/CollectionTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php index b78ed9dc82377..33a2e339fa717 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php @@ -10,8 +10,6 @@ /** * Class CollectionTest - * - * @package Magento\Security\Model\ResourceModel\UserExpiration */ class CollectionTest extends \PHPUnit\Framework\TestCase { From 7fee060ce233387363eea80ca2a69083f8fc9d3d Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Mon, 27 Jan 2020 10:23:37 -0500 Subject: [PATCH 62/70] create DTO for user expiration (#19093) --- .../Api/Data/UserExpirationInterface.php | 50 +++++++++++++++++++ .../Magento/Security/Model/UserExpiration.php | 50 +++++++++++++++++-- app/code/Magento/Security/etc/di.xml | 1 + 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Security/Api/Data/UserExpirationInterface.php diff --git a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php new file mode 100644 index 0000000000000..80fc3f06d8338 --- /dev/null +++ b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Security\Api\Data; + +/** + * Interface UserExpirationInterface to be used as a DTO for expires_at property on User model. + */ +interface UserExpirationInterface +{ + + const EXPIRES_AT = 'expires_at'; + + const USER_ID = 'user_id'; + + /** + * `expires_at` getter. + * + * @return string + */ + public function getExpiresAt(); + + /** + * `expires_at` setter. + * + * @param string $expiresAt + * @return $this + */ + public function setExpiresAt($expiresAt); + + /** + * `user_id` getter. + * + * @return string + */ + public function getUserId(); + + /** + * `user_id` setter. + * + * @param string $userId + * @return $this + */ + public function setUserId($userId); +} diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index 08f60ade672b6..c5c7aeaf68a51 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -7,14 +7,12 @@ namespace Magento\Security\Model; +use Magento\Security\Api\Data\UserExpirationInterface; + /** * Admin User Expiration model. - * @method string getUserId() - * @method \Magento\Security\Model\UserExpiration setUserId($userId) - * @method string getExpiresAt() - * @method \Magento\Security\Model\UserExpiration setExpiresAt($value) */ -class UserExpiration extends \Magento\Framework\Model\AbstractModel +class UserExpiration extends \Magento\Framework\Model\AbstractModel implements UserExpirationInterface { /** @@ -26,4 +24,46 @@ protected function _construct() { $this->_init(\Magento\Security\Model\ResourceModel\UserExpiration::class); } + + /** + * `expires_at` getter. + * + * @return string + */ + public function getExpiresAt() + { + return $this->getData(self::EXPIRES_AT); + } + + /** + * `expires_at` setter. + * + * @param string $expiresAt + * @return $this + */ + public function setExpiresAt($expiresAt) + { + return $this->setData(self::EXPIRES_AT, $expiresAt); + } + + /** + * `user_id` getter. + * + * @return string + */ + public function getUserId() + { + return $this->getData(self::USER_ID); + } + + /** + * `user_id` setter. + * + * @param string $userId + * @return $this + */ + public function setUserId($userId) + { + return $this->setData(self::USER_ID, $userId); + } } diff --git a/app/code/Magento/Security/etc/di.xml b/app/code/Magento/Security/etc/di.xml index 4fe88f219cf74..3b07bb84b1161 100644 --- a/app/code/Magento/Security/etc/di.xml +++ b/app/code/Magento/Security/etc/di.xml @@ -18,4 +18,5 @@ </argument> </arguments> </type> + <preference for="Magento\Security\Api\Data\UserExpirationInterface" type="Magento\Security\Model\UserExpiration"/> </config> From 5e6fdebf20b4e9b084c2874fdee681297c3e8c79 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Sat, 8 Feb 2020 11:54:51 -0500 Subject: [PATCH 63/70] fix integration, unit tests (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/Plugin/AdminUserForm.php | 2 +- app/code/Magento/Security/Model/UserExpiration.php | 2 -- .../{Api/Data => Model}/UserExpirationInterface.php | 2 +- app/code/Magento/Security/Model/UserExpirationManager.php | 2 +- .../Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php | 6 +++--- app/code/Magento/Security/etc/di.xml | 2 +- .../Magento/Security/Model/Plugin/AuthSessionTest.php | 5 +++-- .../Model/ResourceModel/UserExpiration/CollectionTest.php | 2 +- .../Magento/Security/Model/UserExpirationManagerTest.php | 4 ++-- 9 files changed, 13 insertions(+), 14 deletions(-) rename app/code/Magento/Security/{Api/Data => Model}/UserExpirationInterface.php (95%) diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index 106295d5774ff..320809d288246 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -24,7 +24,7 @@ class AdminUserForm private $userExpirationResource; /** - * @var \Magento\Security\Model\UserExpirationFactory + * @var \Magento\Security\Model\UserExpirationInterfaceFactory */ private $userExpirationFactory; diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index c5c7aeaf68a51..b220a8092602f 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -7,8 +7,6 @@ namespace Magento\Security\Model; -use Magento\Security\Api\Data\UserExpirationInterface; - /** * Admin User Expiration model. */ diff --git a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php b/app/code/Magento/Security/Model/UserExpirationInterface.php similarity index 95% rename from app/code/Magento/Security/Api/Data/UserExpirationInterface.php rename to app/code/Magento/Security/Model/UserExpirationInterface.php index 80fc3f06d8338..06f8c258983ba 100644 --- a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php +++ b/app/code/Magento/Security/Model/UserExpirationInterface.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Security\Api\Data; +namespace Magento\Security\Model; /** * Interface UserExpirationInterface to be used as a DTO for expires_at property on User model. diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index 7043a559ca1d4..ffcaa9f8d9457 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -134,7 +134,7 @@ private function processExpiredUsers(ExpiredUsersCollection $expiredRecords): vo public function isUserExpired(string $userId): bool { $isExpired = false; - /** @var \Magento\Security\Model\UserExpiration $expiredRecord */ + /** @var \Magento\Security\Model\UserExpirationInterface $expiredRecord */ $expiredRecord = $this->userExpirationCollectionFactory->create() ->addExpiredRecordsForUserFilter($userId) ->getFirstItem(); diff --git a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php index 403255cbb60f8..8768a2a647f0e 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php @@ -48,7 +48,7 @@ class AdminUserAuthenticateBeforeTest extends \PHPUnit\Framework\TestCase private $eventMock; /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\UserExpiration + * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\UserExpirationInterface */ private $userExpirationMock; @@ -77,8 +77,8 @@ protected function setUp() $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getUsername']); $this->userExpirationMock = $this->createPartialMock( - \Magento\Security\Model\UserExpiration::class, - ['getId', 'getExpiresAt', 'setId', 'setExpiresAt'] + \Magento\Security\Model\UserExpirationInterface::class, + ['getUserId', 'getExpiresAt', 'setUserId', 'setExpiresAt'] ); } diff --git a/app/code/Magento/Security/etc/di.xml b/app/code/Magento/Security/etc/di.xml index 3b07bb84b1161..0e415acb93ab3 100644 --- a/app/code/Magento/Security/etc/di.xml +++ b/app/code/Magento/Security/etc/di.xml @@ -18,5 +18,5 @@ </argument> </arguments> </type> - <preference for="Magento\Security\Api\Data\UserExpirationInterface" type="Magento\Security\Model\UserExpiration"/> + <preference for="Magento\Security\Model\UserExpirationInterface" type="Magento\Security\Model\UserExpiration"/> </config> diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 598cb6daafae1..a7a18716874cf 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -159,8 +159,9 @@ public function testProcessProlongWithExpiredUser() /** @var \Magento\User\Model\User $user */ $user = $this->objectManager->create(\Magento\User\Model\User::class); $user->loadByUsername(\Magento\TestFramework\Bootstrap::ADMIN_NAME); - $userExpirationFactory = $this->objectManager->create(\Magento\Security\Model\UserExpirationFactory::class); - /** @var \Magento\Security\Model\UserExpiration $userExpiration */ + $userExpirationFactory = + $this->objectManager->create(\Magento\Security\Model\UserExpirationInterfaceFactory::class); + /** @var \Magento\Security\Model\UserExpirationInterface $userExpiration */ $userExpiration = $userExpirationFactory->create(); $userExpiration->setId($user->getId()) ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php index 33a2e339fa717..e52f84c68d851 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/ResourceModel/UserExpiration/CollectionTest.php @@ -9,7 +9,7 @@ namespace Magento\Security\Model\ResourceModel\UserExpiration; /** - * Class CollectionTest + * Test UserExpiration collection filters. */ class CollectionTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index 9ab7287c414a6..e8f99d6759302 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -156,8 +156,8 @@ private function expireUser(\Magento\User\Model\User $user) { $expireDate = new \DateTime(); $expireDate->modify('-10 days'); - /** @var \Magento\Security\Model\UserExpiration $userExpiration */ - $userExpiration = $this->objectManager->create(\Magento\Security\Model\UserExpiration::class); + /** @var \Magento\Security\Model\UserExpirationInterface $userExpiration */ + $userExpiration = $this->objectManager->create(\Magento\Security\Model\UserExpirationInterface::class); $userExpiration->setId($user->getId()) ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) ->save(); From b03b0a065a08c9e6643f11dd5edaf100cff00e76 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 3 Mar 2020 11:32:43 -0500 Subject: [PATCH 64/70] fix date format, test names (magento/magento2#22833: Short-term admin accounts) --- .../Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml | 4 ++-- .../Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml index 919d2523927fa..02a8170b308d3 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithInvalidExpirationTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminUserCreateNewUserWithInvalidExpirationTest"> + <test name="AdminCreateNewUserWithInvalidExpirationTest"> <annotations> <features value="Security"/> <stories value="Create new user with invalid expiration date."/> @@ -26,7 +26,7 @@ </after> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="-5 day" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> + <generateDate date="-5 day" format="M j, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml index a00872d848472..dc971f2044760 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminCreateNewUserWithValidExpirationTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminUserCreateNewUserWithValidExpirationTest"> + <test name="AdminCreateNewUserWithValidExpirationTest"> <annotations> <features value="Security"/> <stories value="Create new user with valid expiration date."/> @@ -25,7 +25,7 @@ </after> <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> - <generateDate date="+5 day" format="M d, Y g:i:s A" stepKey="expiresDateTime"/> + <generateDate date="+5 day" format="M j, Y g:i:s A" stepKey="expiresDateTime"/> <actionGroup ref="AdminFillInUserWithExpirationActionGroup" stepKey="fillInNewUserWithValidExpiration"> <argument name="expires_at" value="{$expiresDateTime}"/> </actionGroup> From c8a41b7d1867679281c0445eb79767a674c6819b Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 3 Mar 2020 13:00:58 -0500 Subject: [PATCH 65/70] make constants private scope (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/UserExpirationInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Model/UserExpirationInterface.php b/app/code/Magento/Security/Model/UserExpirationInterface.php index 06f8c258983ba..d7699f162c469 100644 --- a/app/code/Magento/Security/Model/UserExpirationInterface.php +++ b/app/code/Magento/Security/Model/UserExpirationInterface.php @@ -14,9 +14,9 @@ interface UserExpirationInterface { - const EXPIRES_AT = 'expires_at'; + private const EXPIRES_AT = 'expires_at'; - const USER_ID = 'user_id'; + private const USER_ID = 'user_id'; /** * `expires_at` getter. From 107cb5f599b9533a1453a7e0cec4365460561667 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 3 Mar 2020 13:09:57 -0500 Subject: [PATCH 66/70] revert constant private scope (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Model/UserExpirationInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Model/UserExpirationInterface.php b/app/code/Magento/Security/Model/UserExpirationInterface.php index d7699f162c469..d422227f65d2b 100644 --- a/app/code/Magento/Security/Model/UserExpirationInterface.php +++ b/app/code/Magento/Security/Model/UserExpirationInterface.php @@ -14,9 +14,9 @@ interface UserExpirationInterface { - private const EXPIRES_AT = 'expires_at'; + public const EXPIRES_AT = 'expires_at'; - private const USER_ID = 'user_id'; + public const USER_ID = 'user_id'; /** * `expires_at` getter. From bef0bd5e51f30c3538eebb09ee45d135d625e09b Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Tue, 3 Mar 2020 19:14:05 -0500 Subject: [PATCH 67/70] move interface into Api/Data, make it extensible (magento/magento2#22833: Short-term admin accounts) --- .../Data}/UserExpirationInterface.php | 19 ++++++++++++++++-- .../Security/Model/Plugin/AdminUserForm.php | 2 +- .../Magento/Security/Model/UserExpiration.php | 20 ++++++++++++++++++- .../Security/Model/UserExpirationManager.php | 2 +- .../AdminUserAuthenticateBeforeTest.php | 13 +++++++++--- app/code/Magento/Security/etc/di.xml | 2 +- .../Security/Model/Plugin/AuthSessionTest.php | 4 ++-- .../Model/UserExpirationManagerTest.php | 4 ++-- 8 files changed, 53 insertions(+), 13 deletions(-) rename app/code/Magento/Security/{Model => Api/Data}/UserExpirationInterface.php (55%) diff --git a/app/code/Magento/Security/Model/UserExpirationInterface.php b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php similarity index 55% rename from app/code/Magento/Security/Model/UserExpirationInterface.php rename to app/code/Magento/Security/Api/Data/UserExpirationInterface.php index d422227f65d2b..3a930aeb584ab 100644 --- a/app/code/Magento/Security/Model/UserExpirationInterface.php +++ b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php @@ -6,12 +6,12 @@ declare(strict_types=1); -namespace Magento\Security\Model; +namespace Magento\Security\Api\Data; /** * Interface UserExpirationInterface to be used as a DTO for expires_at property on User model. */ -interface UserExpirationInterface +interface UserExpirationInterface extends \Magento\Framework\Api\ExtensibleDataInterface { public const EXPIRES_AT = 'expires_at'; @@ -47,4 +47,19 @@ public function getUserId(); * @return $this */ public function setUserId($userId); + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Security\Api\Data\UserExpirationExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes(\Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes); } diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index 320809d288246..ad3c352b7f369 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -24,7 +24,7 @@ class AdminUserForm private $userExpirationResource; /** - * @var \Magento\Security\Model\UserExpirationInterfaceFactory + * @var \Magento\Security\Api\Data\UserExpirationInterfaceFactory */ private $userExpirationFactory; diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index b220a8092602f..51a41b5c0a5be 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -7,10 +7,12 @@ namespace Magento\Security\Model; +use Magento\Security\Api\Data\UserExpirationInterface; + /** * Admin User Expiration model. */ -class UserExpiration extends \Magento\Framework\Model\AbstractModel implements UserExpirationInterface +class UserExpiration extends \Magento\Framework\Model\AbstractExtensibleModel implements UserExpirationInterface { /** @@ -64,4 +66,20 @@ public function setUserId($userId) { return $this->setData(self::USER_ID, $userId); } + + /** + * @inheritDoc + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(\Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } } diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index ffcaa9f8d9457..b9645ba52ff67 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -134,7 +134,7 @@ private function processExpiredUsers(ExpiredUsersCollection $expiredRecords): vo public function isUserExpired(string $userId): bool { $isExpired = false; - /** @var \Magento\Security\Model\UserExpirationInterface $expiredRecord */ + /** @var \Magento\Security\Api\Data\UserExpirationInterface $expiredRecord */ $expiredRecord = $this->userExpirationCollectionFactory->create() ->addExpiredRecordsForUserFilter($userId) ->getFirstItem(); diff --git a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php index 8768a2a647f0e..3f5717abe2a2c 100644 --- a/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php +++ b/app/code/Magento/Security/Test/Unit/Observer/AdminUserAuthenticateBeforeTest.php @@ -48,7 +48,7 @@ class AdminUserAuthenticateBeforeTest extends \PHPUnit\Framework\TestCase private $eventMock; /** - * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Model\UserExpirationInterface + * @var \PHPUnit\Framework\MockObject\MockObject|\Magento\Security\Api\Data\UserExpirationInterface */ private $userExpirationMock; @@ -77,8 +77,15 @@ protected function setUp() $this->eventObserverMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); $this->eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getUsername']); $this->userExpirationMock = $this->createPartialMock( - \Magento\Security\Model\UserExpirationInterface::class, - ['getUserId', 'getExpiresAt', 'setUserId', 'setExpiresAt'] + \Magento\Security\Api\Data\UserExpirationInterface::class, + [ + 'getUserId', + 'getExpiresAt', + 'setUserId', + 'setExpiresAt', + 'getExtensionAttributes', + 'setExtensionAttributes' + ] ); } diff --git a/app/code/Magento/Security/etc/di.xml b/app/code/Magento/Security/etc/di.xml index 0e415acb93ab3..3b07bb84b1161 100644 --- a/app/code/Magento/Security/etc/di.xml +++ b/app/code/Magento/Security/etc/di.xml @@ -18,5 +18,5 @@ </argument> </arguments> </type> - <preference for="Magento\Security\Model\UserExpirationInterface" type="Magento\Security\Model\UserExpiration"/> + <preference for="Magento\Security\Api\Data\UserExpirationInterface" type="Magento\Security\Model\UserExpiration"/> </config> diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index a7a18716874cf..b0c333737bfb8 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -160,8 +160,8 @@ public function testProcessProlongWithExpiredUser() $user = $this->objectManager->create(\Magento\User\Model\User::class); $user->loadByUsername(\Magento\TestFramework\Bootstrap::ADMIN_NAME); $userExpirationFactory = - $this->objectManager->create(\Magento\Security\Model\UserExpirationInterfaceFactory::class); - /** @var \Magento\Security\Model\UserExpirationInterface $userExpiration */ + $this->objectManager->create(\Magento\Security\Api\Data\UserExpirationInterfaceFactory::class); + /** @var \Magento\Security\Api\Data\UserExpirationInterface $userExpiration */ $userExpiration = $userExpirationFactory->create(); $userExpiration->setId($user->getId()) ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php index e8f99d6759302..adb7b7a120f1f 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/UserExpirationManagerTest.php @@ -156,8 +156,8 @@ private function expireUser(\Magento\User\Model\User $user) { $expireDate = new \DateTime(); $expireDate->modify('-10 days'); - /** @var \Magento\Security\Model\UserExpirationInterface $userExpiration */ - $userExpiration = $this->objectManager->create(\Magento\Security\Model\UserExpirationInterface::class); + /** @var \Magento\Security\Api\Data\UserExpirationInterface $userExpiration */ + $userExpiration = $this->objectManager->create(\Magento\Security\Api\Data\UserExpirationInterface::class); $userExpiration->setId($user->getId()) ->setExpiresAt($expireDate->format('Y-m-d H:i:s')) ->save(); From 6ef861e2accc2e91db4b0e0e08c3732e0dad7907 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Wed, 4 Mar 2020 11:22:40 -0500 Subject: [PATCH 68/70] use fully qualified class names (magento/magento2#22833: Short-term admin accounts) --- .../Api/Data/UserExpirationInterface.php | 8 +++-- .../Security/Model/Plugin/AdminUserForm.php | 22 ++++++++----- .../Model/Plugin/UserValidationRules.php | 8 +++-- .../Magento/Security/Model/UserExpiration.php | 6 ++-- .../Model/UserExpiration/Validator.php | 14 ++++---- .../Security/Model/UserExpirationManager.php | 33 +++++++++++-------- .../Observer/AdminUserAuthenticateBefore.php | 9 ++--- .../Security/Observer/AfterAdminUserSave.php | 15 +++++---- 8 files changed, 68 insertions(+), 47 deletions(-) diff --git a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php index 3a930aeb584ab..e3c89ae253b01 100644 --- a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php +++ b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php @@ -8,6 +8,8 @@ namespace Magento\Security\Api\Data; +use \Magento\Security\Api\Data\UserExpirationExtensionInterface; + /** * Interface UserExpirationInterface to be used as a DTO for expires_at property on User model. */ @@ -51,15 +53,15 @@ public function setUserId($userId); /** * Retrieve existing extension attributes object or create a new one. * - * @return \Magento\Security\Api\Data\UserExpirationExtensionInterface|null + * @return UserExpirationExtensionInterface|null */ public function getExtensionAttributes(); /** * Set an extension attributes object. * - * @param \Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes + * @param UserExpirationExtensionInterface $extensionAttributes * @return $this */ - public function setExtensionAttributes(\Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes); + public function setExtensionAttributes(UserExpirationExtensionInterface $extensionAttributes); } diff --git a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php index ad3c352b7f369..0c024d15ef4ae 100644 --- a/app/code/Magento/Security/Model/Plugin/AdminUserForm.php +++ b/app/code/Magento/Security/Model/Plugin/AdminUserForm.php @@ -7,6 +7,10 @@ namespace Magento\Security\Model\Plugin; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Security\Model\ResourceModel\UserExpiration; +use Magento\Security\Model\UserExpirationFactory; + /** * Add the `expires_at` form field to the User main form. */ @@ -14,31 +18,31 @@ class AdminUserForm { /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ private $localeDate; /** - * @var \Magento\Security\Model\ResourceModel\UserExpiration + * @var UserExpiration */ private $userExpirationResource; /** - * @var \Magento\Security\Api\Data\UserExpirationInterfaceFactory + * @var UserExpirationFactory */ private $userExpirationFactory; /** * UserForm constructor. * - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory - * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + * @param TimezoneInterface $localeDate + * @param UserExpirationFactory $userExpirationFactory + * @param UserExpiration $userExpirationResource */ public function __construct( - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, - \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + TimezoneInterface $localeDate, + UserExpirationFactory $userExpirationFactory, + UserExpiration $userExpirationResource ) { $this->localeDate = $localeDate; $this->userExpirationResource = $userExpirationResource; diff --git a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php index 00d5bc13b65e8..7fddbb21200f4 100644 --- a/app/code/Magento/Security/Model/Plugin/UserValidationRules.php +++ b/app/code/Magento/Security/Model/Plugin/UserValidationRules.php @@ -7,20 +7,22 @@ namespace Magento\Security\Model\Plugin; +use Magento\Security\Model\UserExpiration\Validator; + /** * \Magento\User\Model\UserValidationRules decorator */ class UserValidationRules { - /**@var \Magento\Security\Model\UserExpiration\Validator */ + /**@var Validator */ private $validator; /** * UserValidationRules constructor. * - * @param \Magento\Security\Model\UserExpiration\Validator $validator + * @param Validator $validator */ - public function __construct(\Magento\Security\Model\UserExpiration\Validator $validator) + public function __construct(Validator $validator) { $this->validator = $validator; } diff --git a/app/code/Magento/Security/Model/UserExpiration.php b/app/code/Magento/Security/Model/UserExpiration.php index 51a41b5c0a5be..e6c711b7ac049 100644 --- a/app/code/Magento/Security/Model/UserExpiration.php +++ b/app/code/Magento/Security/Model/UserExpiration.php @@ -7,12 +7,14 @@ namespace Magento\Security\Model; +use Magento\Framework\Model\AbstractExtensibleModel; +use Magento\Security\Api\Data\UserExpirationExtensionInterface; use Magento\Security\Api\Data\UserExpirationInterface; /** * Admin User Expiration model. */ -class UserExpiration extends \Magento\Framework\Model\AbstractExtensibleModel implements UserExpirationInterface +class UserExpiration extends AbstractExtensibleModel implements UserExpirationInterface { /** @@ -78,7 +80,7 @@ public function getExtensionAttributes() /** * @inheritDoc */ - public function setExtensionAttributes(\Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes) + public function setExtensionAttributes(UserExpirationExtensionInterface $extensionAttributes) { return $this->_setExtensionAttributes($extensionAttributes); } diff --git a/app/code/Magento/Security/Model/UserExpiration/Validator.php b/app/code/Magento/Security/Model/UserExpiration/Validator.php index 46633db260692..62dbd7852ff33 100644 --- a/app/code/Magento/Security/Model/UserExpiration/Validator.php +++ b/app/code/Magento/Security/Model/UserExpiration/Validator.php @@ -7,6 +7,8 @@ namespace Magento\Security\Model\UserExpiration; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Validator\AbstractValidator; /** @@ -15,21 +17,21 @@ class Validator extends AbstractValidator { - /**@var \Magento\Framework\Stdlib\DateTime\TimezoneInterface */ + /**@var TimezoneInterface */ private $timezone; - /**@var \Magento\Framework\Stdlib\DateTime\DateTime */ + /**@var DateTime */ private $dateTime; /** * Validator constructor. * - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param TimezoneInterface $timezone + * @param DateTime $dateTime */ public function __construct( - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + TimezoneInterface $timezone, + DateTime $dateTime ) { $this->timezone = $timezone; $this->dateTime = $dateTime; diff --git a/app/code/Magento/Security/Model/UserExpirationManager.php b/app/code/Magento/Security/Model/UserExpirationManager.php index b9645ba52ff67..fe6b87de5a8ec 100644 --- a/app/code/Magento/Security/Model/UserExpirationManager.php +++ b/app/code/Magento/Security/Model/UserExpirationManager.php @@ -7,7 +7,12 @@ namespace Magento\Security\Model; +use Magento\Backend\Model\Auth\Session; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory as AdminSessionCollectionFactory; use Magento\Security\Model\ResourceModel\UserExpiration\Collection as ExpiredUsersCollection; +use Magento\Security\Model\ResourceModel\UserExpiration\CollectionFactory as UserExpirationCollectionFactory; +use Magento\User\Model\ResourceModel\User\CollectionFactory as UserCollectionFactory; /** * Class to handle admin user expirations. Temporary admin users can be created with an expiration @@ -19,7 +24,7 @@ class UserExpirationManager { /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var DateTime */ private $dateTime; @@ -34,7 +39,7 @@ class UserExpirationManager private $adminSessionInfoCollectionFactory; /** - * @var \Magento\Backend\Model\Auth\Session + * @var Session */ private $authSession; @@ -44,27 +49,27 @@ class UserExpirationManager private $userExpirationCollectionFactory; /** - * @var \Magento\User\Model\ResourceModel\User\CollectionFactory + * @var UserCollectionFactory */ private $userCollectionFactory; /** * UserExpirationManager constructor. * - * @param \Magento\Backend\Model\Auth\Session $authSession + * @param Session $authSession * @param ConfigInterface $securityConfig - * @param ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionInfoCollectionFactory - * @param ResourceModel\UserExpiration\CollectionFactory $userExpirationCollectionFactory - * @param \Magento\User\Model\ResourceModel\User\CollectionFactory $userCollectionFactory - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param AdminSessionCollectionFactory $adminSessionInfoCollectionFactory + * @param UserExpirationCollectionFactory $userExpirationCollectionFactory + * @param UserCollectionFactory $userCollectionFactory + * @param DateTime $dateTime */ public function __construct( - \Magento\Backend\Model\Auth\Session $authSession, - \Magento\Security\Model\ConfigInterface $securityConfig, - \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionInfoCollectionFactory, - \Magento\Security\Model\ResourceModel\UserExpiration\CollectionFactory $userExpirationCollectionFactory, - \Magento\User\Model\ResourceModel\User\CollectionFactory $userCollectionFactory, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + Session $authSession, + ConfigInterface $securityConfig, + AdminSessionCollectionFactory $adminSessionInfoCollectionFactory, + UserExpirationCollectionFactory $userExpirationCollectionFactory, + UserCollectionFactory $userCollectionFactory, + DateTime $dateTime ) { $this->dateTime = $dateTime; $this->securityConfig = $securityConfig; diff --git a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php index 3c07285afcb32..2d0f7bc0f0ac0 100644 --- a/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php +++ b/app/code/Magento/Security/Observer/AdminUserAuthenticateBefore.php @@ -11,6 +11,7 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\Plugin\AuthenticationException; use Magento\Security\Model\UserExpirationManager; +use Magento\User\Model\UserFactory; /** * Check for expired users. @@ -23,7 +24,7 @@ class AdminUserAuthenticateBefore implements ObserverInterface private $userExpirationManager; /** - * @var \Magento\User\Model\UserFactory + * @var UserFactory */ private $userFactory; @@ -31,11 +32,11 @@ class AdminUserAuthenticateBefore implements ObserverInterface * AdminUserAuthenticateBefore constructor. * * @param UserExpirationManager $userExpirationManager - * @param \Magento\User\Model\UserFactory $userFactory + * @param UserFactory $userFactory */ public function __construct( - \Magento\Security\Model\UserExpirationManager $userExpirationManager, - \Magento\User\Model\UserFactory $userFactory + UserExpirationManager $userExpirationManager, + UserFactory $userFactory ) { $this->userExpirationManager = $userExpirationManager; $this->userFactory = $userFactory; diff --git a/app/code/Magento/Security/Observer/AfterAdminUserSave.php b/app/code/Magento/Security/Observer/AfterAdminUserSave.php index 247e88a720ceb..d11c1bfdcdf17 100644 --- a/app/code/Magento/Security/Observer/AfterAdminUserSave.php +++ b/app/code/Magento/Security/Observer/AfterAdminUserSave.php @@ -9,6 +9,8 @@ use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; +use Magento\Security\Model\ResourceModel\UserExpiration as UserExpirationResource; +use Magento\Security\Model\UserExpirationFactory; /** * Save UserExpiration on admin user record. @@ -16,24 +18,24 @@ class AfterAdminUserSave implements ObserverInterface { /** - * @var \Magento\Security\Model\UserExpirationFactory + * @var UserExpirationFactory */ private $userExpirationFactory; /** - * @var \Magento\Security\Model\ResourceModel\UserExpiration + * @var UserExpirationResource */ private $userExpirationResource; /** * AfterAdminUserSave constructor. * - * @param \Magento\Security\Model\UserExpirationFactory $userExpirationFactory - * @param \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + * @param UserExpirationFactory $userExpirationFactory + * @param UserExpirationResource $userExpirationResource */ public function __construct( - \Magento\Security\Model\UserExpirationFactory $userExpirationFactory, - \Magento\Security\Model\ResourceModel\UserExpiration $userExpirationResource + UserExpirationFactory $userExpirationFactory, + UserExpirationResource $userExpirationResource ) { $this->userExpirationFactory = $userExpirationFactory; @@ -45,6 +47,7 @@ public function __construct( * * @param Observer $observer * @return void + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function execute(Observer $observer) { From d65e609e12f139bd3a2d69697c1bbad361404b60 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Thu, 5 Mar 2020 11:39:26 -0500 Subject: [PATCH 69/70] fix static test failure (magento/magento2#22833: Short-term admin accounts) --- app/code/Magento/Security/Api/Data/UserExpirationInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php index e3c89ae253b01..63cf2b7099c03 100644 --- a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php +++ b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php @@ -53,7 +53,7 @@ public function setUserId($userId); /** * Retrieve existing extension attributes object or create a new one. * - * @return UserExpirationExtensionInterface|null + * @return UserExpirationExtensionInterface */ public function getExtensionAttributes(); From c8cfb5ba4877e8c9eba533a3979693e5d92590f9 Mon Sep 17 00:00:00 2001 From: lfolco <me@laurafolco.com> Date: Thu, 5 Mar 2020 12:28:40 -0500 Subject: [PATCH 70/70] fix static test failure (magento/magento2#22833: Short-term admin accounts) --- .../Magento/Security/Api/Data/UserExpirationInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php index 63cf2b7099c03..4f602c8b58a87 100644 --- a/app/code/Magento/Security/Api/Data/UserExpirationInterface.php +++ b/app/code/Magento/Security/Api/Data/UserExpirationInterface.php @@ -53,14 +53,14 @@ public function setUserId($userId); /** * Retrieve existing extension attributes object or create a new one. * - * @return UserExpirationExtensionInterface + * @return \Magento\Security\Api\Data\UserExpirationExtensionInterface|null */ public function getExtensionAttributes(); /** * Set an extension attributes object. * - * @param UserExpirationExtensionInterface $extensionAttributes + * @param \Magento\Security\Api\Data\UserExpirationExtensionInterface $extensionAttributes * @return $this */ public function setExtensionAttributes(UserExpirationExtensionInterface $extensionAttributes);