diff --git a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php index 6fddee979e07f..0f9b9c2589fa5 100644 --- a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php +++ b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php @@ -41,7 +41,7 @@ public function get($sku, $editMode = false, $storeId = null, $forceReload = fal * * @param int $productId * @param bool $editMode - * @param null|int $storeId + * @param int|null $storeId * @param bool $forceReload * @return \Magento\Catalog\Api\Data\ProductInterface * @throws \Magento\Framework\Exception\NoSuchEntityException diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php index 161b72ada2c70..a498d464eaf94 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php @@ -8,7 +8,7 @@ use Magento\Framework\App\TemplateTypesInterface; use Magento\Framework\Mail\MessageInterface; -class TransportBuilderTest extends \Magento\Framework\Mail\Test\Unit\Template\TransportBuilderTest +class TransportBuilderTest extends \PHPUnit_Framework_TestCase { /** * @var string @@ -20,20 +20,68 @@ class TransportBuilderTest extends \Magento\Framework\Mail\Test\Unit\Template\Tr */ protected $builder; + /** + * @var \Magento\Framework\Mail\Template\FactoryInterface | \PHPUnit_Framework_MockObject_MockObject + */ + protected $templateFactoryMock; + + /** + * @var \Magento\Framework\Mail\Message | \PHPUnit_Framework_MockObject_MockObject + */ + protected $messageMock; + + /** + * @var \Magento\Framework\ObjectManagerInterface | \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectManagerMock; + + /** + * @var \Magento\Framework\Mail\Template\SenderResolverInterface | \PHPUnit_Framework_MockObject_MockObject + */ + protected $senderResolverMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $mailTransportFactoryMock; + + /** + * @return void + */ + public function setUp() + { + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->templateFactoryMock = $this->getMock('Magento\Framework\Mail\Template\FactoryInterface'); + $this->messageMock = $this->getMock('Magento\Framework\Mail\Message'); + $this->objectManagerMock = $this->getMock('Magento\Framework\ObjectManagerInterface'); + $this->senderResolverMock = $this->getMock('Magento\Framework\Mail\Template\SenderResolverInterface'); + $this->mailTransportFactoryMock = $this->getMockBuilder('Magento\Framework\Mail\TransportInterfaceFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->builder = $objectManagerHelper->getObject( + $this->builderClassName, + [ + 'templateFactory' => $this->templateFactoryMock, + 'message' => $this->messageMock, + 'objectManager' => $this->objectManagerMock, + 'senderResolver' => $this->senderResolverMock, + 'mailTransportFactory' => $this->mailTransportFactoryMock + ] + ); + } + /** * @param int $templateType * @param string $messageType * @param string $bodyText - * @param string $templateNamespace * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function testGetTransport( $templateType = TemplateTypesInterface::TYPE_HTML, $messageType = MessageInterface::TYPE_HTML, - $bodyText = '

Html message

', - $templateNamespace = '' + $bodyText = '

Html message

' ) { $filter = $this->getMock('Magento\Email\Model\Template\Filter', [], [], '', false); $data = [ diff --git a/app/code/Magento/Store/Model/Plugin/StoreCookie.php b/app/code/Magento/Store/Model/Plugin/StoreCookie.php index cb4ec64e03a2f..b42e645799867 100644 --- a/app/code/Magento/Store/Model/Plugin/StoreCookie.php +++ b/app/code/Magento/Store/Model/Plugin/StoreCookie.php @@ -52,29 +52,25 @@ public function __construct( * Delete cookie "store" if the store (a value in the cookie) does not exist or is inactive * * @param \Magento\Framework\App\FrontController $subject - * @param callable $proceed * @param \Magento\Framework\App\RequestInterface $request - * @return mixed + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundDispatch( + public function beforeDispatch( \Magento\Framework\App\FrontController $subject, - \Closure $proceed, \Magento\Framework\App\RequestInterface $request ) { - $defaultStore = $this->storeManager->getDefaultStoreView(); $storeCodeFromCookie = $this->storeCookieManager->getStoreCodeFromCookie(); if ($storeCodeFromCookie) { try { $this->storeRepository->getActiveStoreByCode($storeCodeFromCookie); } catch (StoreIsInactiveException $e) { - $this->storeCookieManager->deleteStoreCookie($defaultStore); + $this->storeCookieManager->deleteStoreCookie($this->storeManager->getDefaultStoreView()); } catch (NoSuchEntityException $e) { - $this->storeCookieManager->deleteStoreCookie($defaultStore); + $this->storeCookieManager->deleteStoreCookie($this->storeManager->getDefaultStoreView()); } catch (InvalidArgumentException $e) { - $this->storeCookieManager->deleteStoreCookie($defaultStore); + $this->storeCookieManager->deleteStoreCookie($this->storeManager->getDefaultStoreView()); } } - return $proceed($request); } } diff --git a/app/code/Magento/Store/Model/StoreResolver.php b/app/code/Magento/Store/Model/StoreResolver.php index f609e271ffd6c..b87a5aabf6f71 100644 --- a/app/code/Magento/Store/Model/StoreResolver.php +++ b/app/code/Magento/Store/Model/StoreResolver.php @@ -45,6 +45,11 @@ class StoreResolver implements \Magento\Store\Api\StoreResolverInterface */ protected $scopeCode; + /* + * @var \Magento\Framework\App\RequestInterface + */ + protected $request; + /** * @param \Magento\Store\Api\StoreRepositoryInterface $storeRepository * @param StoreCookieManagerInterface $storeCookieManager diff --git a/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php index 3b2197494bef8..43a471fba9ffc 100644 --- a/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php @@ -37,11 +37,6 @@ class StoreCookieTest extends \PHPUnit_Framework_TestCase */ protected $storeMock; - /** - * @var \Closure - */ - protected $closureMock; - /** * @var \Magento\Framework\App\FrontController|\PHPUnit_Framework_MockObject_MockObject */ @@ -77,10 +72,6 @@ public function setUp() ->setMethods([]) ->getMock(); - $this->closureMock = function () { - return 'ExpectedValue'; - }; - $this->subjectMock = $this->getMockBuilder('Magento\Framework\App\FrontController') ->disableOriginalConstructor() ->setMethods([]) @@ -106,7 +97,7 @@ public function setUp() ); } - public function testAroundDispatchNoSuchEntity() + public function testBeforeDispatchNoSuchEntity() { $storeCode = 'store'; $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); @@ -115,13 +106,10 @@ public function testAroundDispatchNoSuchEntity() ->method('getActiveStoreByCode') ->willThrowException(new NoSuchEntityException); $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); - $this->assertEquals( - 'ExpectedValue', - $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) - ); + $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } - public function testAroundDispatchStoreIsInactive() + public function testBeforeDispatchStoreIsInactive() { $storeCode = 'store'; $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); @@ -130,13 +118,10 @@ public function testAroundDispatchStoreIsInactive() ->method('getActiveStoreByCode') ->willThrowException(new StoreIsInactiveException); $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); - $this->assertEquals( - 'ExpectedValue', - $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) - ); + $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } - public function testAroundDispatchInvalidArgument() + public function testBeforeDispatchInvalidArgument() { $storeCode = 'store'; $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); @@ -145,22 +130,16 @@ public function testAroundDispatchInvalidArgument() ->method('getActiveStoreByCode') ->willThrowException(new InvalidArgumentException); $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); - $this->assertEquals( - 'ExpectedValue', - $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) - ); + $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } - public function testAroundDispatchNoStoreCookie() + public function testBeforeDispatchNoStoreCookie() { $storeCode = null; - $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); $this->storeCookieManagerMock->expects($this->once())->method('getStoreCodeFromCookie')->willReturn($storeCode); + $this->storeManagerMock->expects($this->never())->method('getDefaultStoreView')->willReturn($this->storeMock); $this->storeRepositoryMock->expects($this->never())->method('getActiveStoreByCode'); $this->storeCookieManagerMock->expects($this->never())->method('deleteStoreCookie')->with($this->storeMock); - $this->assertEquals( - 'ExpectedValue', - $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) - ); + $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } } diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index b72ef3a7b1676..3b8d21188b28f 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -281,6 +281,11 @@ + + + Magento\Store\Model\StoreManagerInterface\Proxy + + Magento\Framework\App\Cache\Type\Config diff --git a/app/code/Magento/User/Model/ResourceModel/User.php b/app/code/Magento/User/Model/ResourceModel/User.php index 1c87be70f24ef..6f782c75f3108 100644 --- a/app/code/Magento/User/Model/ResourceModel/User.php +++ b/app/code/Magento/User/Model/ResourceModel/User.php @@ -539,7 +539,7 @@ public function getOldPasswords($user, $retainLimit = 4) $userId = (int)$user->getId(); $table = $this->getTable('admin_passwords'); - // purge expired passwords, except that should retain + // purge expired passwords, except those which should be retained $retainPasswordIds = $this->getConnection()->fetchCol( $this->getConnection() ->select() @@ -556,7 +556,7 @@ public function getOldPasswords($user, $retainLimit = 4) } $this->getConnection()->delete($table, $where); - // now get all remained passwords + // get all remaining passwords return $this->getConnection()->fetchCol( $this->getConnection() ->select() diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index b9573b4f76e30..c5d38e8b98e25 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -256,9 +256,9 @@ protected function _getValidationRulesBeforeSave() } /** - * Validate customer attribute values. - * For existing customer password + confirmation will be validated only when password is set - * (i.e. its change is requested) + * Validate admin user data. + * + * Existing user password confirmation will be validated only when password is set * * @return bool|string[] */ @@ -272,8 +272,35 @@ public function validate() return $validator->getMessages(); } - return true; + return $this->validatePasswordChange(); + } + + /** + * Make sure admin password was changed. + * + * New password is compared to at least 4 previous passwords to prevent setting them again + * + * @return bool|string[] + */ + protected function validatePasswordChange() + { + $password = $this->getPassword(); + if ($password && !$this->getForceNewPassword() && $this->getId()) { + $errorMessage = __('Sorry, but this password has already been used. Please create another.'); + // Check if password is equal to the current one + if ($this->_encryptor->isValidHash($password, $this->getOrigData('password'))) { + return [$errorMessage]; + } + // Check whether password was used before + $passwordHash = $this->_encryptor->getHash($password, false); + foreach ($this->getResource()->getOldPasswords($this) as $oldPasswordHash) { + if ($passwordHash === $oldPasswordHash) { + return [$errorMessage]; + } + } + } + return true; } /** diff --git a/app/code/Magento/User/Observer/Backend/CheckAdminPasswordChangeObserver.php b/app/code/Magento/User/Observer/Backend/CheckAdminPasswordChangeObserver.php deleted file mode 100644 index 3bf06a441e248..0000000000000 --- a/app/code/Magento/User/Observer/Backend/CheckAdminPasswordChangeObserver.php +++ /dev/null @@ -1,82 +0,0 @@ -userResource = $userResource; - $this->encryptor = $encryptor; - } - - /** - * Harden admin password change. - * - * New password must be minimum 7 chars length and include alphanumeric characters - * The password is compared to at least last 4 previous passwords to prevent setting them again - * - * @param EventObserver $observer - * @return void - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function execute(EventObserver $observer) - { - /* @var $user \Magento\User\Model\User */ - $user = $observer->getEvent()->getObject(); - - if ($user->getNewPassword()) { - $password = $user->getNewPassword(); - } else { - $password = $user->getPassword(); - } - - if ($password && !$user->getForceNewPassword() && $user->getId()) { - if ($this->encryptor->isValidHash($password, $user->getOrigData('password'))) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Sorry, but this password has already been used. Please create another.') - ); - } - - // check whether password was used before - $passwordHash = $this->encryptor->getHash($password, false); - foreach ($this->userResource->getOldPasswords($user) as $oldPasswordHash) { - if ($passwordHash === $oldPasswordHash) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Sorry, but this password has already been used. Please create another.') - ); - } - } - } - } -} diff --git a/app/code/Magento/User/Observer/Backend/TrackAdminNewPasswordObserver.php b/app/code/Magento/User/Observer/Backend/TrackAdminNewPasswordObserver.php index 790d78301f058..0f33107ac0173 100644 --- a/app/code/Magento/User/Observer/Backend/TrackAdminNewPasswordObserver.php +++ b/app/code/Magento/User/Observer/Backend/TrackAdminNewPasswordObserver.php @@ -71,7 +71,7 @@ public function __construct( } /** - * Save new admin password + * Save current admin password to prevent its usage when changed in the future. * * @param EventObserver $observer * @return void @@ -81,7 +81,7 @@ public function execute(EventObserver $observer) /* @var $user \Magento\User\Model\User */ $user = $observer->getEvent()->getObject(); if ($user->getId()) { - $password = $user->getNewPassword(); + $password = $user->getCurrentPassword(); $passwordLifetime = $this->observerConfig->getAdminPasswordLifetime(); if ($passwordLifetime && $password && !$user->getForceNewPassword()) { $passwordHash = $this->encryptor->getHash($password, false); diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index ed495d41e369d..d2f89414527de 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -610,4 +610,104 @@ public function testIsResetPasswordLinkTokenExpiredIsNotExpiredToken() $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(1); $this->assertFalse($this->model->isResetPasswordLinkTokenExpired()); } + + public function testCheckPasswordChangeEqualToCurrent() + { + /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + $validatorMock = $this->getMockBuilder('Magento\Framework\Validator\DataObject') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); + $this->validationRulesMock->expects($this->once()) + ->method('addUserInfoRules') + ->with($validatorMock); + $validatorMock->expects($this->once())->method('isValid')->willReturn(true); + + $newPassword = "NEWmYn3wpassw0rd"; + $oldPassword = "OLDmYn3wpassw0rd"; + $this->model->setPassword($newPassword) + ->setId(1) + ->setOrigData('password', $oldPassword); + $this->encryptorMock->expects($this->once()) + ->method('isValidHash') + ->with($newPassword, $oldPassword) + ->willReturn(true); + $result = $this->model->validate(); + $this->assertInternalType('array', $result); + $this->assertCount(1, $result); + $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); + } + + public function testCheckPasswordChangeEqualToPrevious() + { + /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + $validatorMock = $this->getMockBuilder('Magento\Framework\Validator\DataObject') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); + $this->validationRulesMock->expects($this->once()) + ->method('addUserInfoRules') + ->with($validatorMock); + $validatorMock->expects($this->once())->method('isValid')->willReturn(true); + + $newPassword = "NEWmYn3wpassw0rd"; + $newPasswordHash = "new password hash"; + $oldPassword = "OLDmYn3wpassw0rd"; + $this->model->setPassword($newPassword) + ->setId(1) + ->setOrigData('password', $oldPassword); + $this->encryptorMock->expects($this->once()) + ->method('isValidHash') + ->with($newPassword, $oldPassword) + ->willReturn(false); + + $this->encryptorMock->expects($this->once()) + ->method('getHash') + ->with($newPassword, false) + ->willReturn($newPasswordHash); + + $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', $newPasswordHash]); + + $result = $this->model->validate(); + $this->assertInternalType('array', $result); + $this->assertCount(1, $result); + $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); + } + + public function testCheckPasswordChangeValid() + { + /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + $validatorMock = $this->getMockBuilder('Magento\Framework\Validator\DataObject') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); + $this->validationRulesMock->expects($this->once()) + ->method('addUserInfoRules') + ->with($validatorMock); + $validatorMock->expects($this->once())->method('isValid')->willReturn(true); + + $newPassword = "NEWmYn3wpassw0rd"; + $newPasswordHash = "new password hash"; + $oldPassword = "OLDmYn3wpassw0rd"; + $this->model->setPassword($newPassword) + ->setId(1) + ->setOrigData('password', $oldPassword); + $this->encryptorMock->expects($this->once()) + ->method('isValidHash') + ->with($newPassword, $oldPassword) + ->willReturn(false); + + $this->encryptorMock->expects($this->once()) + ->method('getHash') + ->with($newPassword, false) + ->willReturn($newPasswordHash); + + $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', 'hash2']); + + $result = $this->model->validate(); + $this->assertTrue($result); + } } diff --git a/app/code/Magento/User/Test/Unit/Observer/Backend/CheckAdminPasswordChangeObserverTest.php b/app/code/Magento/User/Test/Unit/Observer/Backend/CheckAdminPasswordChangeObserverTest.php deleted file mode 100644 index a4b9b37edbfa8..0000000000000 --- a/app/code/Magento/User/Test/Unit/Observer/Backend/CheckAdminPasswordChangeObserverTest.php +++ /dev/null @@ -1,126 +0,0 @@ -userMock = $this->getMockBuilder('Magento\User\Model\ResourceModel\User') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\EncryptorInterface') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\ManagerInterface') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMockForAbstractClass(); - - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->model = $helper->getObject( - '\Magento\User\Observer\Backend\CheckAdminPasswordChangeObserver', - [ - 'userResource' => $this->userMock, - 'encryptor' => $this->encryptorMock, - ] - ); - } - - public function testCheckAdminPasswordChange() - { - $newPW = "mYn3wpassw0rd"; - $uid = 123; - /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ - $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ - $eventMock = $this->getMockBuilder('Magento\Framework\Event') - ->disableOriginalConstructor() - ->setMethods(['getObject']) - ->getMock(); - - /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ - $userMock = $this->getMockBuilder('Magento\User\Model\User') - ->disableOriginalConstructor() - ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) - ->getMock(); - - $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); - $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); - $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW); - $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false); - $userMock->expects($this->once())->method('getId')->willReturn($uid); - $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(false); - $this->encryptorMock->expects($this->once())->method('getHash')->willReturn(md5($newPW)); - $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]); - - $this->model->execute($eventObserverMock); - } - - public function testCheckAdminPasswordChangeThrowsLocalizedExp() - { - $newPW = "mYn3wpassw0rd"; - $uid = 123; - /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ - $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ - $eventMock = $this->getMockBuilder('Magento\Framework\Event') - ->disableOriginalConstructor() - ->setMethods(['getObject']) - ->getMock(); - - /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ - $userMock = $this->getMockBuilder('Magento\User\Model\User') - ->disableOriginalConstructor() - ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) - ->getMock(); - - $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); - $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); - $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW); - $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false); - $userMock->expects($this->once())->method('getId')->willReturn($uid); - $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(true); - $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]); - - try { - $this->model->execute($eventObserverMock); - } catch (\Magento\Framework\Exception\LocalizedException $expected) { - return; - } - $this->fail('An expected exception has not been raised.'); - } -} diff --git a/app/code/Magento/User/Test/Unit/Observer/Backend/TrackAdminNewPasswordObserverTest.php b/app/code/Magento/User/Test/Unit/Observer/Backend/TrackAdminNewPasswordObserverTest.php index eb1b930771dd3..d6dc5ddde8dc6 100644 --- a/app/code/Magento/User/Test/Unit/Observer/Backend/TrackAdminNewPasswordObserverTest.php +++ b/app/code/Magento/User/Test/Unit/Observer/Backend/TrackAdminNewPasswordObserverTest.php @@ -108,13 +108,13 @@ public function testTrackAdminPassword() /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ $userMock = $this->getMockBuilder('Magento\User\Model\User') ->disableOriginalConstructor() - ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) + ->setMethods(['getId', 'getCurrentPassword', 'getForceNewPassword']) ->getMock(); $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); $userMock->expects($this->once())->method('getId')->willReturn($uid); - $userMock->expects($this->once())->method('getNewPassword')->willReturn($newPW); + $userMock->expects($this->once())->method('getCurrentPassword')->willReturn($newPW); $this->configInterfaceMock ->expects($this->atLeastOnce()) ->method('getValue') diff --git a/app/code/Magento/User/etc/adminhtml/events.xml b/app/code/Magento/User/etc/adminhtml/events.xml index 1bcdab99c7667..469c19b9c1b25 100755 --- a/app/code/Magento/User/etc/adminhtml/events.xml +++ b/app/code/Magento/User/etc/adminhtml/events.xml @@ -12,9 +12,6 @@ - - - diff --git a/app/code/Magento/Webapi/Model/Config/ClassReflector.php b/app/code/Magento/Webapi/Model/Config/ClassReflector.php index 2bd5ac75d0fc7..7aa85ed2732c9 100644 --- a/app/code/Magento/Webapi/Model/Config/ClassReflector.php +++ b/app/code/Magento/Webapi/Model/Config/ClassReflector.php @@ -98,7 +98,7 @@ public function extractMethodData(\Zend\Code\Reflection\MethodReflection $method $methodData['interface']['in']['parameters'][$parameter->getName()] = $parameterData; } $returnType = $this->_typeProcessor->getGetterReturnType($method); - if ($returnType != 'void' && $returnType != 'null') { + if ($returnType['type'] != 'void' && $returnType['type'] != 'null') { $methodData['interface']['out']['parameters']['result'] = [ 'type' => $this->_typeProcessor->register($returnType['type']), 'documentation' => $returnType['description'], diff --git a/app/etc/di.xml b/app/etc/di.xml index 256a424f14f93..bb0f0981894fd 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -128,7 +128,6 @@ - @@ -155,6 +154,20 @@ Magento\Framework\Filesystem\Driver\File + + + + + Magento\Framework\Communication\Config\Reader\XmlReader + 10 + + + Magento\Framework\Communication\Config\Reader\EnvReader + 20 + + + + main diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index 067a539e0ac12..b87cea882de18 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -492,7 +492,7 @@ public function install() private function copyAppConfigFiles() { $globalConfigFiles = glob( - $this->_globalConfigDir . '/{di.xml,vendor_path.php}', + $this->_globalConfigDir . '/{di.xml,*/di.xml,vendor_path.php}', GLOB_BRACE ); foreach ($globalConfigFiles as $file) { diff --git a/dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php index 8900e66587a5d..064884a54f9a1 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php @@ -52,15 +52,6 @@ public function testGetTopicsExceptionMissingRequest() $this->getConfigInstance(__DIR__ . '/_files/communication_missing_request.xml')->getTopics(); } - /** - * @expectedException \LogicException - * @expectedExceptionMessage "handler" element must be declared for topic "customerUpdated", because it has - */ - public function testGetTopicsExceptionMissingHandler() - { - $this->getConfigInstance(__DIR__ . '/_files/communication_missing_handler.xml')->getTopics(); - } - /** * @expectedException \LogicException * @expectedExceptionMessage Service method specified in the definition of topic "customerRetrieved" is not @@ -304,12 +295,20 @@ protected function getConfigInstance($configFilePath, $envConfigFilePath = null) 'methodsMap' => $methodsMap ] ); + $readersConfig = [ + 'xmlReader' => ['reader' => $xmlReader, 'sortOrder' => 10], + 'envReader' => ['reader' => $envReader, 'sortOrder' => 20] + ]; + /** @var \Magento\Framework\Communication\Config\CompositeReader $reader */ + $reader = $objectManager->create( + 'Magento\Framework\Communication\Config\CompositeReader', + ['readers' => $readersConfig] + ); /** @var \Magento\Framework\Communication\Config $config */ $configData = $objectManager->create( 'Magento\Framework\Communication\Config\Data', [ - 'reader' => $xmlReader, - 'envReader' => $envReader + 'reader' => $reader ] ); return $objectManager->create( diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 6b8d0f843ebf4..04984323d7754 100755 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -3879,6 +3879,7 @@ 'Magento\Framework\Component\ComponentRegistrar' ], ['Magento\Framework\App\Router\ActionList\Reader'], + ['Magento\User\Observer\Backend\CheckAdminPasswordChangeObserver'], ['Magento\Framework\View\File\AbstractCollector'], ['Magento\Tools\Migration\Acl\FileManager'], ['Magento\Tools\Migration\Acl\Formatter'], diff --git a/lib/internal/Magento/Framework/App/ObjectManager/Environment/Compiled.php b/lib/internal/Magento/Framework/App/ObjectManager/Environment/Compiled.php index 6718dc4d11830..ee02fd5afcc94 100644 --- a/lib/internal/Magento/Framework/App/ObjectManager/Environment/Compiled.php +++ b/lib/internal/Magento/Framework/App/ObjectManager/Environment/Compiled.php @@ -73,7 +73,7 @@ public function getDiConfig() */ protected function getConfigData() { - $this->getObjectManagerConfigLoader()->load(Area::AREA_GLOBAL); + return $this->getObjectManagerConfigLoader()->load(Area::AREA_GLOBAL); } /** diff --git a/lib/internal/Magento/Framework/App/ObjectManagerFactory.php b/lib/internal/Magento/Framework/App/ObjectManagerFactory.php index 0d482bf1599dd..a9f0b69e2f078 100644 --- a/lib/internal/Magento/Framework/App/ObjectManagerFactory.php +++ b/lib/internal/Magento/Framework/App/ObjectManagerFactory.php @@ -120,10 +120,10 @@ public function create(array $arguments) $definitions = $definitionFactory->createClassDefinition($deploymentConfig->get('definitions')); $relations = $definitionFactory->createRelations(); - /** @var EnvironmentFactory $enFactory */ - $enFactory = new $this->envFactoryClassName($relations, $definitions); + /** @var EnvironmentFactory $envFactory */ + $envFactory = new $this->envFactoryClassName($relations, $definitions); /** @var EnvironmentInterface $env */ - $env = $enFactory->createEnvironment(); + $env = $envFactory->createEnvironment(); /** @var ConfigInterface $diConfig */ $diConfig = $env->getDiConfig(); @@ -176,7 +176,15 @@ public function create(array $arguments) $this->factory->setObjectManager($objectManager); ObjectManager::setInstance($objectManager); - $definitionFactory->getCodeGenerator()->setObjectManager($objectManager); + + $generatorParams = $diConfig->getArguments('Magento\Framework\Code\Generator'); + /** Arguments are stored in different format when DI config is compiled, thus require custom processing */ + $generatedEntities = isset($generatorParams['generatedEntities']['_v_']) + ? $generatorParams['generatedEntities']['_v_'] + : (isset($generatorParams['generatedEntities']) ? $generatorParams['generatedEntities'] : []); + $definitionFactory->getCodeGenerator() + ->setObjectManager($objectManager) + ->setGeneratedEntities($generatedEntities); $env->configureObjectManager($diConfig, $sharedInstances); diff --git a/lib/internal/Magento/Framework/Code/Generator.php b/lib/internal/Magento/Framework/Code/Generator.php index 2a572346e6a60..92057208769f7 100644 --- a/lib/internal/Magento/Framework/Code/Generator.php +++ b/lib/internal/Magento/Framework/Code/Generator.php @@ -22,7 +22,7 @@ class Generator protected $_ioObject; /** - * @var string[] of EntityAbstract classes + * @var array */ protected $_generatedEntities; @@ -57,13 +57,25 @@ public function __construct( /** * Get generated entities * - * @return string[] + * @return array */ public function getGeneratedEntities() { return $this->_generatedEntities; } + /** + * Set entity-to-generator map + * + * @param array $generatedEntities + * @return $this + */ + public function setGeneratedEntities($generatedEntities) + { + $this->_generatedEntities = $generatedEntities; + return $this; + } + /** * Generate Class * diff --git a/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php b/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php new file mode 100644 index 0000000000000..5033b7a7cd2cc --- /dev/null +++ b/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php @@ -0,0 +1,61 @@ +readers = []; + foreach ($readers as $readerInfo) { + if (!isset($readerInfo['reader'])) { + continue; + } + $this->readers[] = $readerInfo['reader']; + } + } + + /** + * Read config. + * + * @param string|null $scope + * @return array + */ + public function read($scope = null) + { + $result = []; + foreach ($this->readers as $reader) { + $result = array_replace_recursive($result, $reader->read($scope)); + } + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Communication/Config/Data.php b/lib/internal/Magento/Framework/Communication/Config/Data.php index c8d9c560bff22..fda3ee8aadb30 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Data.php +++ b/lib/internal/Magento/Framework/Communication/Config/Data.php @@ -13,18 +13,15 @@ class Data extends \Magento\Framework\Config\Data /** * Initialize dependencies. * - * @param \Magento\Framework\Communication\Config\Reader\XmlReader $reader + * @param \Magento\Framework\Communication\Config\CompositeReader $reader * @param \Magento\Framework\Config\CacheInterface $cache - * @param \Magento\Framework\Communication\Config\Reader\EnvReader $envReader * @param string $cacheId */ public function __construct( - \Magento\Framework\Communication\Config\Reader\XmlReader $reader, + \Magento\Framework\Communication\Config\CompositeReader $reader, \Magento\Framework\Config\CacheInterface $cache, - \Magento\Framework\Communication\Config\Reader\EnvReader $envReader, $cacheId = 'communication_config_cache' ) { parent::__construct($reader, $cache, $cacheId); - $this->merge($envReader->read()); } } diff --git a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php index 3406614738aad..7633e660276c5 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php +++ b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php @@ -7,7 +7,7 @@ use Magento\Framework\Communication\ConfigInterface as Config; use Magento\Framework\Phrase; -use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Communication\Config\ReflectionGenerator; use Magento\Framework\Stdlib\BooleanUtils; use Magento\Framework\Communication\Config\Reader\XmlReader\Validator; @@ -19,9 +19,9 @@ class Converter implements \Magento\Framework\Config\ConverterInterface const SERVICE_METHOD_NAME_PATTERN = '/^([a-zA-Z\\\\]+)::([a-zA-Z]+)$/'; /** - * @var MethodsMap + * @var ReflectionGenerator */ - private $methodsMap; + private $reflectionGenerator; /** * @var BooleanUtils @@ -36,16 +36,16 @@ class Converter implements \Magento\Framework\Config\ConverterInterface /** * Initialize dependencies * - * @param MethodsMap $methodsMap + * @param ReflectionGenerator $reflectionGenerator * @param BooleanUtils $booleanUtils * @param Validator $xmlValidator */ public function __construct( - MethodsMap $methodsMap, + ReflectionGenerator $reflectionGenerator, BooleanUtils $booleanUtils, Validator $xmlValidator ) { - $this->methodsMap = $methodsMap; + $this->reflectionGenerator = $reflectionGenerator; $this->booleanUtils = $booleanUtils; $this->xmlValidator = $xmlValidator; } @@ -78,7 +78,13 @@ protected function extractTopics($config) $topicAttributes = $topicNode->attributes; $topicName = $topicAttributes->getNamedItem('name')->nodeValue; - $requestResponseSchema = $this->extractSchemaDefinedByServiceMethod($topicNode); + $serviceMethod = $this->getServiceMethodBySchema($topicNode); + $requestResponseSchema = $serviceMethod + ? $this->reflectionGenerator->extractMethodMetadata( + $serviceMethod['typeName'], + $serviceMethod['methodName'] + ) + : null; $requestSchema = $this->extractTopicRequestSchema($topicNode); $responseSchema = $this->extractTopicResponseSchema($topicNode); $handlers = $this->extractTopicResponseHandlers($topicNode); @@ -95,16 +101,13 @@ protected function extractTopics($config) $requestSchema, $responseSchema ); - if ($requestResponseSchema) { - $output[$topicName] = [ - Config::TOPIC_NAME => $topicName, - Config::TOPIC_IS_SYNCHRONOUS => true, - Config::TOPIC_REQUEST => $requestResponseSchema[Config::SCHEMA_METHOD_PARAMS], - Config::TOPIC_REQUEST_TYPE => Config::TOPIC_REQUEST_TYPE_METHOD, - Config::TOPIC_RESPONSE => $requestResponseSchema[Config::SCHEMA_METHOD_RETURN_TYPE], - Config::TOPIC_HANDLERS => $handlers - ?: ['defaultHandler' => $requestResponseSchema[Config::SCHEMA_METHOD_HANDLER]] - ]; + if ($serviceMethod) { + $output[$topicName] = $this->reflectionGenerator->generateTopicConfigForServiceMethod( + $topicName, + $serviceMethod['typeName'], + $serviceMethod['methodName'], + $handlers + ); } else if ($requestSchema && $responseSchema) { $output[$topicName] = [ Config::TOPIC_NAME => $topicName, @@ -149,11 +152,11 @@ protected function extractTopicResponseHandlers($topicNode) continue; } $handlerName = $handlerAttributes->getNamedItem('name')->nodeValue; - $serviceName = $handlerAttributes->getNamedItem('type')->nodeValue; + $serviceType = $handlerAttributes->getNamedItem('type')->nodeValue; $methodName = $handlerAttributes->getNamedItem('method')->nodeValue; - $this->xmlValidator->validateResponseHandlersType($serviceName, $methodName, $handlerName, $topicName); + $this->xmlValidator->validateResponseHandlersType($serviceType, $methodName, $handlerName, $topicName); $handlerNodes[$handlerName] = [ - Config::HANDLER_TYPE => $serviceName, + Config::HANDLER_TYPE => $serviceType, Config::HANDLER_METHOD => $methodName ]; } @@ -198,37 +201,19 @@ protected function extractTopicResponseSchema($topicNode) } /** - * Get message schema defined by service method signature. + * Get service class and method specified in schema attribute. * * @param \DOMNode $topicNode - * @return array + * @return array|null Contains class name and method name */ - protected function extractSchemaDefinedByServiceMethod($topicNode) + protected function getServiceMethodBySchema($topicNode) { $topicAttributes = $topicNode->attributes; if (!$topicAttributes->getNamedItem('schema')) { return null; } $topicName = $topicAttributes->getNamedItem('name')->nodeValue; - list($className, $methodName) = $this->parseServiceMethod( - $topicAttributes->getNamedItem('schema')->nodeValue, - $topicName - ); - $result = [ - Config::SCHEMA_METHOD_PARAMS => [], - Config::SCHEMA_METHOD_RETURN_TYPE => $this->methodsMap->getMethodReturnType($className, $methodName), - Config::SCHEMA_METHOD_HANDLER => [Config::HANDLER_TYPE => $className, Config::HANDLER_METHOD => $methodName] - ]; - $paramsMeta = $this->methodsMap->getMethodParams($className, $methodName); - foreach ($paramsMeta as $paramPosition => $paramMeta) { - $result[Config::SCHEMA_METHOD_PARAMS][] = [ - Config::SCHEMA_METHOD_PARAM_NAME => $paramMeta[MethodsMap::METHOD_META_NAME], - Config::SCHEMA_METHOD_PARAM_POSITION => $paramPosition, - Config::SCHEMA_METHOD_PARAM_IS_REQUIRED => !$paramMeta[MethodsMap::METHOD_META_HAS_DEFAULT_VALUE], - Config::SCHEMA_METHOD_PARAM_TYPE => $paramMeta[MethodsMap::METHOD_META_TYPE], - ]; - } - return $result; + return $this->parseServiceMethod($topicAttributes->getNamedItem('schema')->nodeValue, $topicName); } /** @@ -236,7 +221,7 @@ protected function extractSchemaDefinedByServiceMethod($topicNode) * * @param string $serviceMethod * @param string $topicName - * @return string[] Contains class name and method name, in a call-back compatible format + * @return array Contains class name and method name */ protected function parseServiceMethod($serviceMethod, $topicName) { @@ -244,6 +229,6 @@ protected function parseServiceMethod($serviceMethod, $topicName) $className = $matches[1]; $methodName = $matches[2]; $this->xmlValidator->validateServiceMethod($serviceMethod, $topicName, $className, $methodName); - return [$className, $methodName]; + return ['typeName' => $className, 'methodName' => $methodName]; } } diff --git a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Validator.php b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Validator.php index c3a61bd3f8bc7..086b70c7e5e48 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Validator.php +++ b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Validator.php @@ -98,14 +98,6 @@ public function validateResponseRequest( ) ); } - if ($responseSchema && !$handlers) { - throw new \LogicException( - sprintf( - '"handler" element must be declared for topic "%s", because it has "response" declared', - $topicName - ) - ); - } if (($requestResponseSchema || $responseSchema) && (count($handlers) >= 2)) { throw new \LogicException( sprintf( diff --git a/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php b/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php new file mode 100644 index 0000000000000..51d99d6a72b69 --- /dev/null +++ b/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php @@ -0,0 +1,82 @@ +methodsMap = $methodsMap; + } + + /** + * Extract service method metadata. + * + * @param string $className + * @param string $methodName + * @return array + */ + public function extractMethodMetadata($className, $methodName) + { + $result = [ + Config::SCHEMA_METHOD_PARAMS => [], + Config::SCHEMA_METHOD_RETURN_TYPE => $this->methodsMap->getMethodReturnType($className, $methodName), + Config::SCHEMA_METHOD_HANDLER => [Config::HANDLER_TYPE => $className, Config::HANDLER_METHOD => $methodName] + ]; + $paramsMeta = $this->methodsMap->getMethodParams($className, $methodName); + foreach ($paramsMeta as $paramPosition => $paramMeta) { + $result[Config::SCHEMA_METHOD_PARAMS][] = [ + Config::SCHEMA_METHOD_PARAM_NAME => $paramMeta[MethodsMap::METHOD_META_NAME], + Config::SCHEMA_METHOD_PARAM_POSITION => $paramPosition, + Config::SCHEMA_METHOD_PARAM_IS_REQUIRED => !$paramMeta[MethodsMap::METHOD_META_HAS_DEFAULT_VALUE], + Config::SCHEMA_METHOD_PARAM_TYPE => $paramMeta[MethodsMap::METHOD_META_TYPE], + ]; + } + return $result; + } + + /** + * Generate config data based on service method signature. + * + * @param string $topicName + * @param string $serviceType + * @param string $serviceMethod + * @param array|null $handlers + * @return array + */ + public function generateTopicConfigForServiceMethod($topicName, $serviceType, $serviceMethod, $handlers = []) + { + $methodMetadata = $this->extractMethodMetadata($serviceType, $serviceMethod); + $returnType = $methodMetadata[Config::SCHEMA_METHOD_RETURN_TYPE]; + $returnType = ($returnType != 'void' && $returnType != 'null') ? $returnType : null; + return [ + Config::TOPIC_NAME => $topicName, + Config::TOPIC_IS_SYNCHRONOUS => $returnType ? true : false, + Config::TOPIC_REQUEST => $methodMetadata[Config::SCHEMA_METHOD_PARAMS], + Config::TOPIC_REQUEST_TYPE => Config::TOPIC_REQUEST_TYPE_METHOD, + Config::TOPIC_RESPONSE => $returnType, + Config::TOPIC_HANDLERS => $handlers + ?: [self::DEFAULT_HANDLER => $methodMetadata[Config::SCHEMA_METHOD_HANDLER]] + ]; + } +} diff --git a/lib/internal/Magento/Framework/Data/Form/FormKey.php b/lib/internal/Magento/Framework/Data/Form/FormKey.php index 6a5485d7bb4f2..55d841ff35194 100644 --- a/lib/internal/Magento/Framework/Data/Form/FormKey.php +++ b/lib/internal/Magento/Framework/Data/Form/FormKey.php @@ -22,16 +22,24 @@ class FormKey */ protected $session; + /** + * @var \Magento\Framework\Escaper + */ + protected $escaper; + /** * @param \Magento\Framework\Math\Random $mathRandom * @param \Magento\Framework\Session\SessionManagerInterface $session + * @param \Magento\Framework\Escaper $escaper */ public function __construct( \Magento\Framework\Math\Random $mathRandom, - \Magento\Framework\Session\SessionManagerInterface $session + \Magento\Framework\Session\SessionManagerInterface $session, + \Magento\Framework\Escaper $escaper ) { $this->mathRandom = $mathRandom; $this->session = $session; + $this->escaper = $escaper; } /** @@ -44,7 +52,7 @@ public function getFormKey() if (!$this->isPresent()) { $this->set($this->mathRandom->getRandomString(16)); } - return $this->session->getData(self::FORM_KEY); + return $this->escaper->escapeHtmlAttr($this->session->getData(self::FORM_KEY)); } /** diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php index 5adb36c4ccef7..de92b3b911c69 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php @@ -22,6 +22,11 @@ class FormKeyTest extends \PHPUnit_Framework_TestCase */ protected $sessionMock; + /** + * @var \Zend\Escaper\Escaper|\PHPUnit_Framework_MockObject_MockObject + */ + protected $escaperMock; + /** * @var FormKey */ @@ -32,9 +37,12 @@ protected function setUp() $this->mathRandomMock = $this->getMock('Magento\Framework\Math\Random', [], [], '', false); $methods = ['setData', 'getData']; $this->sessionMock = $this->getMock('Magento\Framework\Session\SessionManager', $methods, [], '', false); + $this->escaperMock = $this->getMock('Magento\Framework\Escaper', [], [], '', false); + $this->escaperMock->expects($this->any())->method('escapeHtmlAttr')->willReturnArgument(0); $this->formKey = new FormKey( $this->mathRandomMock, - $this->sessionMock + $this->sessionMock, + $this->escaperMock ); } diff --git a/lib/internal/Magento/Framework/Escaper.php b/lib/internal/Magento/Framework/Escaper.php index f28374eb0ac61..c6792dc93fa89 100644 --- a/lib/internal/Magento/Framework/Escaper.php +++ b/lib/internal/Magento/Framework/Escaper.php @@ -8,7 +8,7 @@ /** * Magento escape methods */ -class Escaper +class Escaper extends \Zend\Escaper\Escaper { /** * Escape html entities diff --git a/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php b/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php index 72d30d3373709..9de7bfe1ba66f 100644 --- a/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php +++ b/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php @@ -44,7 +44,11 @@ class MaliciousCode implements \Zend_Filter_Interface */ public function filter($value) { - return preg_replace($this->_expressions, '', $value); + $replaced = 0; + do { + $value = preg_replace($this->_expressions, '', $value, -1, $replaced); + } while ($replaced !== 0); + return $value; } /** diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php index dfe0a755397cc..5c6e4be9d8364 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php @@ -101,6 +101,10 @@ public function filterDataProvider() 'Base64' => [ 'Embedded Image', 'Embedded Image', + ], + 'Nested malicious tags' => [ + 'pt>alert(1);pt>', + 'alert(1);', ] ]; } diff --git a/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php b/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php index 6c2f8db4da1e0..37a1896f565c2 100644 --- a/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php +++ b/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php @@ -177,22 +177,7 @@ public function getCodeGenerator() $this->_filesystemDriver, $this->_generationDir ); - $this->codeGenerator = new \Magento\Framework\Code\Generator( - $generatorIo, - [ - Generator\Factory::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Code\Generator\Factory', - Generator\Proxy::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Code\Generator\Proxy', - Generator\Repository::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Code\Generator\Repository', - Generator\Persistor::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Code\Generator\Persistor', - InterceptionGenerator\Interceptor::ENTITY_TYPE => '\Magento\Framework\Interception\Code\Generator\Interceptor', - MapperGenerator::ENTITY_TYPE => '\Magento\Framework\Api\Code\Generator\Mapper', - SearchResults::ENTITY_TYPE => '\Magento\Framework\Api\Code\Generator\SearchResults', - ConverterGenerator::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Code\Generator\Converter', - ProfilerGenerator\Logger::ENTITY_TYPE => '\Magento\Framework\ObjectManager\Profiler\Code\Generator\Logger', - ExtensionAttributesGenerator::ENTITY_TYPE => 'Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator', - ExtensionAttributesInterfaceGenerator::ENTITY_TYPE => 'Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator' - ] - ); + $this->codeGenerator = new \Magento\Framework\Code\Generator($generatorIo); } return $this->codeGenerator; } diff --git a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php index 24e3a990a9677..a8ce1d6a565d7 100644 --- a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php @@ -678,11 +678,16 @@ protected function classHasMethod(ClassReflection $class, $methodName) * @param string $serviceName API service name * @param string $methodName * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function processInterfaceCallInfo($interface, $serviceName, $methodName) { foreach ($interface as $direction => $interfaceData) { $direction = ($direction == 'in') ? 'requiredInput' : 'returned'; + if ($direction == 'returned' && !isset($interfaceData['parameters'])) { + /** No return value means that service method is asynchronous */ + return $this; + } foreach ($interfaceData['parameters'] as $parameterData) { if (!$this->isTypeSimple($parameterData['type']) && !$this->isTypeAny($parameterData['type'])) { $operation = $this->getOperationName($serviceName, $methodName); diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php index b9dbc3aebbda2..711daf317ced7 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -94,7 +94,7 @@ protected function configure() { $this->setName(self::NAME) ->setDescription( - 'Generates DI configuration and all non-existing interceptors and factories' + 'Generates DI configuration and all missing classes that can be auto-generated' ); parent::configure(); } diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php index fc5eb973944b5..789166966a4f2 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php @@ -30,6 +30,7 @@ use Magento\Setup\Module\Di\Definition\Serializer\Igbinary; use Magento\Setup\Module\Di\Definition\Serializer\Standard; use \Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Code\Generator as CodeGenerator; /** * Command to generate all non-existing proxies and factories, and pre-compile class definitions, @@ -87,7 +88,7 @@ class DiCompileMultiTenantCommand extends AbstractSetupCommand /** * - * @var \Magento\Framework\Code\Generator + * @var CodeGenerator */ private $generator; @@ -274,26 +275,13 @@ public function generateCode($generationDir, $fileExcludePatterns, $input) $interceptorScanner = new Scanner\XmlInterceptorScanner(); $this->entities['interceptors'] = $interceptorScanner->collectEntities($this->files['di']); // 1.2 Generation of Factory and Additional Classes - $generatorIo = new \Magento\Framework\Code\Generator\Io( - new \Magento\Framework\Filesystem\Driver\File(), - $generationDir + $generatorIo = $this->objectManager->create( + 'Magento\Framework\Code\Generator\Io', + ['generationDirectory' => $generationDir] ); - $this->generator = new \Magento\Framework\Code\Generator( - $generatorIo, - [ - Interceptor::ENTITY_TYPE => 'Magento\Framework\Interception\Code\Generator\Interceptor', - Proxy::ENTITY_TYPE => 'Magento\Framework\ObjectManager\Code\Generator\Proxy', - Factory::ENTITY_TYPE => 'Magento\Framework\ObjectManager\Code\Generator\Factory', - Mapper::ENTITY_TYPE => 'Magento\Framework\Api\Code\Generator\Mapper', - Persistor::ENTITY_TYPE => 'Magento\Framework\ObjectManager\Code\Generator\Persistor', - Repository::ENTITY_TYPE => 'Magento\Framework\ObjectManager\Code\Generator\Repository', - Converter::ENTITY_TYPE => 'Magento\Framework\ObjectManager\Code\Generator\Converter', - SearchResults::ENTITY_TYPE => 'Magento\Framework\Api\Code\Generator\SearchResults', - ExtensionAttributesInterfaceGenerator::ENTITY_TYPE => - 'Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator', - ExtensionAttributesGenerator::ENTITY_TYPE => - 'Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator' - ] + $this->generator = $this->objectManager->create( + 'Magento\Framework\Code\Generator', + ['ioObject' => $generatorIo] ); /** Initialize object manager for code generation based on configs */ $this->generator->setObjectManager($this->objectManager); @@ -302,13 +290,13 @@ public function generateCode($generationDir, $fileExcludePatterns, $input) foreach ($repositories as $entityName) { switch ($this->generator->generateClass($entityName)) { - case \Magento\Framework\Code\Generator::GENERATION_SUCCESS: + case CodeGenerator::GENERATION_SUCCESS: $this->log->add(Log::GENERATION_SUCCESS, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_ERROR: + case CodeGenerator::GENERATION_ERROR: $this->log->add(Log::GENERATION_ERROR, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_SKIP: + case CodeGenerator::GENERATION_SKIP: default: //no log break; @@ -318,13 +306,13 @@ public function generateCode($generationDir, $fileExcludePatterns, $input) sort($this->entities[$type]); foreach ($this->entities[$type] as $entityName) { switch ($this->generator->generateClass($entityName)) { - case \Magento\Framework\Code\Generator::GENERATION_SUCCESS: + case CodeGenerator::GENERATION_SUCCESS: $this->log->add(Log::GENERATION_SUCCESS, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_ERROR: + case CodeGenerator::GENERATION_ERROR: $this->log->add(Log::GENERATION_ERROR, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_SKIP: + case CodeGenerator::GENERATION_SKIP: default: //no log break; @@ -388,13 +376,13 @@ private function compileCode($generationDir, $fileExcludePatterns, $input) foreach (['interceptors', 'di'] as $type) { foreach ($this->entities[$type] as $entityName) { switch ($this->generator->generateClass($entityName)) { - case \Magento\Framework\Code\Generator::GENERATION_SUCCESS: + case CodeGenerator::GENERATION_SUCCESS: $this->log->add(Log::GENERATION_SUCCESS, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_ERROR: + case CodeGenerator::GENERATION_ERROR: $this->log->add(Log::GENERATION_ERROR, $entityName); break; - case \Magento\Framework\Code\Generator::GENERATION_SKIP: + case CodeGenerator::GENERATION_SKIP: default: //no log break;